Merge commit 'bb3db97402a1650c05d9c50e813c89972adfa6ad' into 1.7.11-dev
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index f34e2bc..200240c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -464,9 +464,6 @@
Position callerPosition,
Origin origin) {
checkIfObsolete();
- if (accessFlags.isSynchronized()) {
- throw new Unreachable("Invalid attempt to build synchronized method for inlining");
- }
return code.buildInliningIR(
context, this, appView, valueNumberGenerator, callerPosition, origin);
}
@@ -1011,7 +1008,10 @@
checkIfObsolete();
assert !accessFlags.isStatic();
Builder builder =
- builder(this).promoteToStatic().unsetOptimizationInfo().withoutThisParameter();
+ builder(this)
+ .promoteToStatic()
+ .withoutThisParameter()
+ .adjustOptimizationInfoAfterRemovingThisParameter();
DexEncodedMethod method = builder.build();
method.copyMetadata(this);
setObsolete();
@@ -1274,11 +1274,6 @@
return this;
}
- public Builder unsetOptimizationInfo() {
- optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
- return this;
- }
-
public Builder withoutThisParameter() {
assert code != null;
if (code.isDexCode()) {
@@ -1289,6 +1284,14 @@
return this;
}
+ public Builder adjustOptimizationInfoAfterRemovingThisParameter() {
+ if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+ optimizationInfo.asUpdatableMethodOptimizationInfo()
+ .adjustOptimizationInfoAfterRemovingThisParameter();
+ }
+ return this;
+ }
+
public void setCode(Code code) {
this.code = code;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 0ca8fac..e540f39 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -145,7 +145,8 @@
boolean encodedValueStorageIsLive;
if (enqueuer.isFieldLive(encodedValueStorage)) {
if (enqueuer.isFieldRead(encodedValueStorage)
- || enqueuer.isFieldWrittenOutsideDefaultConstructor(encodedValueStorage)) {
+ || enqueuer.isFieldWrittenOutsideDefaultConstructor(encodedValueStorage)
+ || reachesMapOrRequiredField(protoFieldInfo)) {
// Mark that the field is both read and written by reflection such that we do not
// (i) optimize field reads into loading the default value of the field or (ii) remove
// field writes to proto fields that could be read using reflection by the proto
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index fa9384a..78018f9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -214,6 +214,13 @@
return normals.build();
}
+ public int numberOfNormalSuccessors() {
+ if (hasCatchHandlers()) {
+ return successors.size() - catchHandlers.getUniqueTargets().size();
+ }
+ return successors.size();
+ }
+
public boolean hasUniquePredecessor() {
return predecessors.size() == 1;
}
@@ -893,11 +900,40 @@
catchHandlers = new CatchHandlers<>(guards, successorIndexes);
}
- public void addCatchHandler(BasicBlock rethrowBlock, DexType guard) {
- assert !hasCatchHandlers();
- getMutableSuccessors().add(0, rethrowBlock);
- rethrowBlock.getMutablePredecessors().add(this);
- catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
+ public void appendCatchHandler(BasicBlock target, DexType guard) {
+ if (!canThrow()) {
+ // Nothing to catch.
+ return;
+ }
+ if (hasCatchHandlers()) {
+ if (catchHandlers.getGuards().contains(guard)) {
+ // Subsumed by an existing catch handler.
+ return;
+ }
+ int targetIndex = successors.indexOf(target);
+ if (targetIndex < 0) {
+ List<BasicBlock> successors = getMutableSuccessors();
+ int numberOfSuccessors = successors.size();
+ int numberOfNormalSuccessors = numberOfNormalSuccessors();
+ if (numberOfNormalSuccessors > 0) {
+ // Increase the size of the successor list by 1, and increase the index of each normal
+ // successor by 1.
+ targetIndex = numberOfSuccessors - numberOfNormalSuccessors;
+ successors.add(targetIndex, target);
+ } else {
+ // If there are no normal successors we can simply add the new catch handler.
+ targetIndex = successors.size();
+ successors.add(target);
+ }
+ target.getMutablePredecessors().add(this);
+ }
+ catchHandlers = catchHandlers.appendGuard(guard, targetIndex);
+ } else {
+ assert instructions.stream().filter(Instruction::instructionTypeCanThrow).count() == 1;
+ getMutableSuccessors().add(0, target);
+ target.getMutablePredecessors().add(this);
+ catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
+ }
}
/**
@@ -1795,8 +1831,7 @@
continue;
}
BasicBlock catchSuccessor = fromBlock.successors.get(prevCatchTarget);
- // We assume that all the catch handlers targets has only one
- // predecessor and, thus, no phis.
+ // We assume that all the catch handlers targets has only one predecessor and, thus, no phis.
assert catchSuccessor.getPredecessors().size() == 1;
assert catchSuccessor.getPhis().isEmpty();
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 237c0cc..95ee7e0 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
@@ -76,6 +76,13 @@
&& getGuards().get(getGuards().size() - 1) == factory.throwableType;
}
+ public CatchHandlers<T> appendGuard(DexType guard, T target) {
+ assert !guards.contains(guard);
+ List<DexType> newGuards = ImmutableList.<DexType>builder().addAll(guards).add(guard).build();
+ List<T> newTargets = ImmutableList.<T>builder().addAll(targets).add(target).build();
+ return new CatchHandlers<>(newGuards, newTargets);
+ }
+
public CatchHandlers<T> removeGuard(DexType guardToBeRemoved) {
List<DexType> newGuards = new ArrayList<>();
List<T> newTargets = new ArrayList<>();
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 fee2c94..ce02bf6 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
@@ -13,12 +13,15 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -133,11 +136,6 @@
return metadata;
}
- public void mergeMetadataFromInlinee(IRCode inlinee) {
- assert !inlinee.metadata.mayHaveMonitorInstruction();
- this.metadata.merge(inlinee.metadata);
- }
-
public BasicBlock entryBlock() {
return blocks.getFirst();
}
@@ -286,6 +284,53 @@
return false;
}
+ /**
+ * Prepares every block for getting catch handlers. This involves splitting blocks until each
+ * block has only one throwing instruction, and all instructions after the throwing instruction
+ * are allowed to follow a throwing instruction.
+ *
+ * <p>It is also a requirement that the entry block does not have any catch handlers, thus the
+ * entry block is split to ensure that it contains no throwing instructions.
+ *
+ * <p>This method also inserts a split block between each block with more than two predecessors
+ * and the predecessors that have a throwing instruction. This is necessary because adding catch
+ * handlers to a predecessor would otherwise lead to critical edges.
+ */
+ public void prepareBlocksForCatchHandlers() {
+ BasicBlock entryBlock = entryBlock();
+ ListIterator<BasicBlock> blockIterator = listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ InstructionListIterator instructionIterator = block.listIterator(this);
+ boolean hasSeenThrowingInstruction = false;
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ boolean instructionTypeCanThrow = instruction.instructionTypeCanThrow();
+ if ((hasSeenThrowingInstruction && !instruction.isAllowedAfterThrowingInstruction())
+ || (instructionTypeCanThrow && block == entryBlock)) {
+ instructionIterator.previous();
+ instructionIterator.split(this, blockIterator);
+ blockIterator.previous();
+ break;
+ }
+ if (instructionTypeCanThrow) {
+ hasSeenThrowingInstruction = true;
+ }
+ }
+ if (hasSeenThrowingInstruction) {
+ List<BasicBlock> successors = block.getSuccessors();
+ if (successors.size() == 1 && ListUtils.first(successors).getPredecessors().size() > 1) {
+ BasicBlock splitBlock = block.createSplitBlock(getHighestBlockNumber() + 1, true);
+ Goto newGoto = new Goto(block);
+ newGoto.setPosition(Position.none());
+ splitBlock.listIterator(this).add(newGoto);
+ blockIterator.add(splitBlock);
+ }
+ }
+ }
+ assert blocks.stream().allMatch(block -> block.numberOfThrowingInstructions() <= 1);
+ }
+
public void splitCriticalEdges() {
List<BasicBlock> newBlocks = new ArrayList<>();
int nextBlockNumber = getHighestBlockNumber() + 1;
@@ -871,6 +916,10 @@
return this::instructionIterator;
}
+ public <T extends Instruction> Iterable<T> instructions(Predicate<Instruction> predicate) {
+ return () -> IteratorUtils.filter(instructionIterator(), predicate);
+ }
+
public InstructionIterator instructionIterator() {
return new IRCodeInstructionIterator(this);
}
@@ -959,6 +1008,10 @@
return createValue(typeLattice, null);
}
+ public Phi createPhi(BasicBlock block, TypeLatticeElement type) {
+ return new Phi(valueNumberGenerator.next(), block, type, null, RegisterReadType.NORMAL);
+ }
+
public ConstNumber createIntConstant(int value) {
Value out = createValue(TypeLatticeElement.INT);
return new ConstNumber(out, value);
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 31a1562..5bddf13 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
@@ -880,6 +880,10 @@
return false;
}
+ public boolean isMonitorEnter() {
+ return false;
+ }
+
public Monitor asMonitor() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index a0eda99..78b9b64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -99,6 +99,11 @@
}
@Override
+ public boolean isMonitorEnter() {
+ return isEnter();
+ }
+
+ @Override
public Monitor asMonitor() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 07c99e0..33b06c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -239,6 +239,10 @@
return definition;
}
+ public boolean hasAliasedValue() {
+ return getAliasedValue() != this;
+ }
+
/**
* If this value is defined by an instruction that defines an alias of another value, such as the
* {@link Assume} instruction, then the incoming value to the {@link Assume} instruction is
@@ -989,11 +993,6 @@
|| typeLattice.nullability().isDefinitelyNotNull();
}
- public boolean canBeNull() {
- assert typeLattice.isReference();
- return typeLattice.isNullable();
- }
-
public void markAsArgument() {
assert !isArgument;
assert !isThis;
@@ -1004,6 +1003,20 @@
return isArgument;
}
+ public int computeArgumentPosition(IRCode code) {
+ assert isArgument;
+ int position = 0;
+ InstructionIterator instructionIterator = code.entryBlock().iterator();
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ assert instruction.isArgument();
+ if (instruction.outValue() == this) {
+ return position;
+ }
+ position++;
+ }
+ throw new Unreachable();
+ }
public boolean knownToBeBoolean() {
return knownToBeBoolean(null);
@@ -1199,10 +1212,20 @@
lattice = aliasedValue.definition.asAssumeDynamicType().getAssumption().getType();
// For precision, verify that the dynamic type is at least as precise as the static type.
- assert lattice.lessThanOrEqualUpToNullability(getTypeLattice(), appView);
+ assert lattice.lessThanOrEqualUpToNullability(typeLattice, appView)
+ : typeLattice + " < " + lattice;
} else {
// Otherwise, simply use the static type.
- lattice = getTypeLattice();
+ lattice = typeLattice;
+ }
+
+ // Account for nullability, which could be flown from non-null assumption in between dynamic
+ // type assumption or simply from array/object creation.
+ if (typeLattice.isDefinitelyNotNull() && lattice.isNullable()) {
+ // Having non-null assumption means it is a reference type.
+ assert lattice.isReference();
+ // Then, we can return the non-null variant of dynamic type if both assumptions are aliased.
+ return lattice.asReferenceTypeLatticeElement().asMeetWithNotNull();
}
return lattice;
}
@@ -1226,7 +1249,11 @@
// Assume<DynamicTypeAssumption>.
Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
if (aliasedValue != null) {
- return aliasedValue.definition.asAssumeDynamicType().getAssumption().getLowerBoundType();
+ ClassTypeLatticeElement lattice =
+ aliasedValue.definition.asAssumeDynamicType().getAssumption().getLowerBoundType();
+ return lattice != null && typeLattice.isDefinitelyNotNull() && lattice.isNullable()
+ ? lattice.asMeetWithNotNull().asClassTypeLatticeElement()
+ : lattice;
}
return null;
}
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 57425ab..dd3416f 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
@@ -5,7 +5,6 @@
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
-import static com.android.tools.r8.ir.optimize.CodeRewriter.checksNullBeforeSideEffect;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -26,17 +25,10 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
-import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
import com.android.tools.r8.ir.analysis.TypeChecker;
import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
import com.android.tools.r8.ir.analysis.fieldaccess.FieldBitAccessAnalysis;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.FieldValueAnalysis;
-import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
-import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
@@ -74,6 +66,7 @@
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -107,7 +100,6 @@
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.util.ArrayList;
-import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -171,6 +163,8 @@
final DeadCodeRemover deadCodeRemover;
+ final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
+
private final OptimizationFeedbackDelayed delayedOptimizationFeedback =
new OptimizationFeedbackDelayed();
private final OptimizationFeedback simpleOptimizationFeedback =
@@ -211,6 +205,7 @@
.map(prefix -> "L" + DescriptorUtils.getPackageBinaryNameFromJavaType(prefix))
.map(options.itemFactory::createString)
.collect(Collectors.toList());
+ this.methodOptimizationInfoCollector = new MethodOptimizationInfoCollector(appView);
if (options.isDesugaredLibraryCompilation()) {
// Specific L8 Settings.
// BackportedMethodRewriter is needed for retarget core library members and backports.
@@ -1403,7 +1398,7 @@
appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
}
// Computation of non-null parameters on normal exits rely on the existence of non-null IRs.
- nonNullTracker.computeNonNullParamOnNormalExits(feedback, code);
+ methodOptimizationInfoCollector.computeNonNullParamOnNormalExits(feedback, code);
}
if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
codeRewriter.removeAssumeInstructions(code);
@@ -1426,21 +1421,25 @@
// Compute optimization info summary for the current method unless it is pinned
// (in that case we should not be making any assumptions about the behavior of the method).
if (!appView.appInfo().withLiveness().isPinned(method.method)) {
- codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
- codeRewriter.identifyParameterUsages(method, code, feedback);
- codeRewriter.identifyReturnsArgument(method, code, feedback);
- codeRewriter.identifyTrivialInitializer(method, code, feedback);
+ methodOptimizationInfoCollector.identifyClassInlinerEligibility(method, code, feedback);
+ methodOptimizationInfoCollector.identifyParameterUsages(method, code, feedback);
+ methodOptimizationInfoCollector.identifyReturnsArgument(method, code, feedback);
+ methodOptimizationInfoCollector.identifyTrivialInitializer(method, code, feedback);
if (options.enableInlining && inliner != null) {
- codeRewriter.identifyInvokeSemanticsForInlining(method, code, appView, feedback);
+ methodOptimizationInfoCollector
+ .identifyInvokeSemanticsForInlining(method, code, appView, feedback);
}
- computeDynamicReturnType(feedback, method, code);
+ methodOptimizationInfoCollector
+ .computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
FieldValueAnalysis.run(appView, code, feedback, method);
- computeInitializedClassesOnNormalExit(feedback, method, code);
- computeMayHaveSideEffects(feedback, method, code);
- computeReturnValueOnlyDependsOnArguments(feedback, method, code);
- computeNonNullParamOrThrow(feedback, method, code);
+ methodOptimizationInfoCollector
+ .computeInitializedClassesOnNormalExit(feedback, method, code);
+ methodOptimizationInfoCollector.computeMayHaveSideEffects(feedback, method, code);
+ methodOptimizationInfoCollector
+ .computeReturnValueOnlyDependsOnArguments(feedback, method, code);
+ methodOptimizationInfoCollector.computeNonNullParamOrThrow(feedback, method, code);
}
}
@@ -1471,164 +1470,6 @@
finalizeIR(method, code, feedback);
}
- // Track usage of parameters and compute their nullability and possibility of NPE.
- private void computeNonNullParamOrThrow(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
- return;
- }
-
- List<Value> arguments = code.collectArguments();
- BitSet paramsCheckedForNull = new BitSet();
- for (int index = 0; index < arguments.size(); index++) {
- Value argument = arguments.get(index);
- // This handles cases where the parameter is checked via Kotlin Intrinsics:
- //
- // kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
- //
- // or its inlined version:
- //
- // if (param != null) return;
- // invoke-static throwParameterIsNullException(msg)
- //
- // or some other variants, e.g., throw null or NPE after the direct null check.
- if (argument.isUsed() && checksNullBeforeSideEffect(code, argument, appView)) {
- paramsCheckedForNull.set(index);
- }
- }
- if (paramsCheckedForNull.length() > 0) {
- feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
- }
- }
-
- private void computeDynamicReturnType(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- if (dynamicTypeOptimization != null) {
- DexType staticReturnTypeRaw = method.method.proto.returnType;
- if (!staticReturnTypeRaw.isReferenceType()) {
- return;
- }
-
- TypeLatticeElement dynamicReturnType =
- dynamicTypeOptimization.computeDynamicReturnType(method, code);
- if (dynamicReturnType != null) {
- TypeLatticeElement staticReturnType =
- TypeLatticeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
-
- // If the dynamic return type is not more precise than the static return type there is no
- // need to record it.
- if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) {
- feedback.methodReturnsObjectOfType(method, appView, dynamicReturnType);
- }
- }
-
- ClassTypeLatticeElement exactReturnType =
- dynamicTypeOptimization.computeDynamicLowerBoundType(method, code);
- if (exactReturnType != null) {
- feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType);
- }
- }
- }
-
- private void computeInitializedClassesOnNormalExit(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- if (options.enableInitializedClassesAnalysis && appView.appInfo().hasLiveness()) {
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- Set<DexType> initializedClasses =
- InitializedClassesOnNormalExitAnalysis.computeInitializedClassesOnNormalExit(
- appViewWithLiveness, code);
- if (initializedClasses != null && !initializedClasses.isEmpty()) {
- feedback.methodInitializesClassesOnNormalExit(method, initializedClasses);
- }
- }
- }
-
- private void computeMayHaveSideEffects(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- // If the method is native, we don't know what could happen.
- assert !method.accessFlags.isNative();
-
- if (!options.enableSideEffectAnalysis) {
- return;
- }
-
- if (appView.appInfo().withLiveness().mayHaveSideEffects.containsKey(method.method)) {
- return;
- }
-
- DexType context = method.method.holder;
-
- if (method.isClassInitializer()) {
- // For class initializers, we also wish to compute if the class initializer has observable
- // side effects.
- ClassInitializerSideEffect classInitializerSideEffect =
- ClassInitializerSideEffectAnalysis.classInitializerCanBePostponed(appView, code);
- if (classInitializerSideEffect.isNone()) {
- feedback.methodMayNotHaveSideEffects(method);
- feedback.classInitializerMayBePostponed(method);
- } else if (classInitializerSideEffect.canBePostponed()) {
- feedback.classInitializerMayBePostponed(method);
- }
- return;
- }
-
- boolean mayHaveSideEffects;
- if (method.accessFlags.isSynchronized()) {
- // If the method is synchronized then it acquires a lock.
- mayHaveSideEffects = true;
- } else if (method.isInstanceInitializer() && hasNonTrivialFinalizeMethod(context)) {
- // If a class T overrides java.lang.Object.finalize(), then treat the constructor as having
- // side effects. This ensures that we won't remove instructions on the form `new-instance
- // {v0}, T`.
- mayHaveSideEffects = true;
- } else {
- // Otherwise, check if there is an instruction that has side effects.
- mayHaveSideEffects =
- Streams.stream(code.instructions())
- .anyMatch(instruction -> instruction.instructionMayHaveSideEffects(appView, context));
- }
-
- if (!mayHaveSideEffects) {
- feedback.methodMayNotHaveSideEffects(method);
- }
- }
-
- private void computeReturnValueOnlyDependsOnArguments(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- if (!options.enableDeterminismAnalysis) {
- return;
- }
- boolean returnValueOnlyDependsOnArguments =
- DeterminismAnalysis.returnValueOnlyDependsOnArguments(appView.withLiveness(), code);
- if (returnValueOnlyDependsOnArguments) {
- feedback.methodReturnValueOnlyDependsOnArguments(method);
- }
- }
-
- // Returns true if `method` is an initializer and the enclosing class overrides the method
- // `void java.lang.Object.finalize()`.
- private boolean hasNonTrivialFinalizeMethod(DexType type) {
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null) {
- if (clazz.isProgramClass() && !clazz.isInterface()) {
- ResolutionResult resolutionResult =
- appView
- .appInfo()
- .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMethods.finalize);
- for (DexEncodedMethod target : resolutionResult.asListOfTargets()) {
- if (target.method != appView.dexItemFactory().objectMethods.finalize) {
- return true;
- }
- }
- return false;
- } else {
- // Conservatively report that the library class could implement finalize().
- return true;
- }
- }
- return false;
- }
-
private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
code.traceBlocks();
if (options.isGeneratingClassFiles()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 708cabb8..d60ac1f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -172,6 +172,16 @@
private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
DexMethod methodToInstall =
methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+ if (dexClass.isInterface()
+ && originalMethod.isDefaultMethod()
+ && !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
+ // Interface method desugaring has been performed before and all the call-backs will be
+ // generated in all implementors of the interface. R8 cannot introduce new
+ // default methods at this point, but R8 does not need to do anything (the interface
+ // already implements the vivified version through inheritance, and all implementors
+ // support the call-back correctly).
+ return;
+ }
CfCode cfCode =
new APIConverterWrapperCfCodeProvider(
appView, originalMethod.method, null, this, dexClass.isInterface())
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 7c1590c..932c817 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,7 +4,6 @@
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.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
@@ -15,12 +14,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
-import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexItemFactory.ThrowableMethods;
@@ -28,7 +22,6 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -54,7 +47,6 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.InstanceOf;
-import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -74,7 +66,6 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Switch;
@@ -85,17 +76,12 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LongInterval;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Supplier;
@@ -140,8 +126,6 @@
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
-import java.util.function.BiFunction;
-import java.util.function.Function;
import java.util.function.Predicate;
public class CodeRewriter {
@@ -441,7 +425,7 @@
BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition(), guard, appView);
code.blocks.add(rethrowBlock);
// Add catch handler to the block containing the last recursive call.
- newBlock.addCatchHandler(rethrowBlock, guard);
+ newBlock.appendCatchHandler(rethrowBlock, guard);
}
}
@@ -1155,614 +1139,6 @@
assert code.isConsistentGraph();
}
- public void identifyReturnsArgument(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- List<BasicBlock> normalExits = code.computeNormalExitBlocks();
- if (normalExits.isEmpty()) {
- feedback.methodNeverReturnsNormally(method);
- return;
- }
- Return firstExit = normalExits.get(0).exit().asReturn();
- if (firstExit.isReturnVoid()) {
- return;
- }
- Value returnValue = firstExit.returnValue();
- boolean isNeverNull = returnValue.getTypeLattice().isReference() && returnValue.isNeverNull();
- for (int i = 1; i < normalExits.size(); i++) {
- Return exit = normalExits.get(i).exit().asReturn();
- Value value = exit.returnValue();
- if (value != returnValue) {
- returnValue = null;
- }
- isNeverNull &= value.getTypeLattice().isReference() && value.isNeverNull();
- }
- if (returnValue != null) {
- if (returnValue.isArgument()) {
- // Find the argument number.
- int index = code.collectArguments().indexOf(returnValue);
- assert index != -1;
- feedback.methodReturnsArgument(method, index);
- }
- if (returnValue.isConstant()) {
- if (returnValue.definition.isConstNumber()) {
- long value = returnValue.definition.asConstNumber().getRawValue();
- feedback.methodReturnsConstantNumber(method, value);
- } else if (returnValue.definition.isConstString()) {
- ConstString constStringInstruction = returnValue.definition.asConstString();
- if (!constStringInstruction.instructionInstanceCanThrow()) {
- feedback.methodReturnsConstantString(method, constStringInstruction.getValue());
- }
- }
- }
- }
- if (isNeverNull) {
- feedback.methodNeverReturnsNull(method);
- }
- }
-
- public void identifyInvokeSemanticsForInlining(
- DexEncodedMethod method, IRCode code, AppView<?> appView, OptimizationFeedback feedback) {
- if (method.isStatic()) {
- // Identifies if the method preserves class initialization after inlining.
- 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, receiver, appView));
- }
- }
-
- public void identifyClassInlinerEligibility(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- // Method eligibility is calculated in similar way for regular method
- // and for the constructor. To be eligible method should only be using its
- // receiver in the following ways:
- //
- // (1) as a receiver of reads/writes of instance fields of the holder class
- // (2) as a return value
- // (3) as a receiver of a call to the superclass initializer. Note that we don't
- // check what is passed to superclass initializer as arguments, only check
- // that it is not the instance being initialized.
- //
- boolean instanceInitializer = method.isInstanceInitializer();
- if (method.accessFlags.isNative()
- || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)
- || method.accessFlags.isSynchronized()) {
- return;
- }
-
- feedback.setClassInlinerEligibility(method, null); // To allow returns below.
-
- Value receiver = code.getThis();
- if (receiver.numberOfPhiUsers() > 0) {
- return;
- }
-
- DexClass clazz = appView.definitionFor(method.method.holder);
- if (clazz == null) {
- return;
- }
-
- boolean receiverUsedAsReturnValue = false;
- boolean seenSuperInitCall = false;
- for (Instruction insn : receiver.uniqueUsers()) {
- if (insn.isReturn()) {
- receiverUsedAsReturnValue = true;
- continue;
- }
-
- if (insn.isInstanceGet() || insn.isInstancePut()) {
- if (insn.isInstancePut()) {
- InstancePut instancePutInstruction = insn.asInstancePut();
- // Only allow field writes to the receiver.
- if (instancePutInstruction.object() != receiver) {
- return;
- }
- // Do not allow the receiver to escape via a field write.
- if (instancePutInstruction.value() == receiver) {
- return;
- }
- }
- DexField field = insn.asFieldInstruction().getField();
- if (field.holder == clazz.type && clazz.lookupInstanceField(field) != null) {
- // Require only accessing instance fields of the *current* class.
- continue;
- }
- return;
- }
-
- // If this is an instance initializer allow one call to superclass instance initializer.
- if (insn.isInvokeDirect()) {
- InvokeDirect invokedDirect = insn.asInvokeDirect();
- DexMethod invokedMethod = invokedDirect.getInvokedMethod();
- if (dexItemFactory.isConstructor(invokedMethod) &&
- invokedMethod.holder == clazz.superType &&
- invokedDirect.inValues().lastIndexOf(receiver) == 0 &&
- !seenSuperInitCall &&
- instanceInitializer) {
- seenSuperInitCall = true;
- continue;
- }
- // We don't support other direct calls yet.
- return;
- }
-
- // Other receiver usages make the method not eligible.
- return;
- }
-
- if (instanceInitializer && !seenSuperInitCall) {
- // Call to super constructor not found?
- return;
- }
-
- feedback.setClassInlinerEligibility(
- method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
- }
-
- public void identifyTrivialInitializer(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- if (!method.isInstanceInitializer() && !method.isClassInitializer()) {
- return;
- }
-
- if (method.accessFlags.isNative()) {
- return;
- }
-
- DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
- if (clazz == null) {
- return;
- }
-
- feedback.setTrivialInitializer(
- method,
- method.isInstanceInitializer()
- ? computeInstanceInitializerInfo(code, clazz, appView::definitionFor)
- : computeClassInitializerInfo(code, clazz));
- }
-
- public void identifyParameterUsages(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- List<ParameterUsage> usages = new ArrayList<>();
- List<Value> values = code.collectArguments();
- for (int i = 0; i < values.size(); i++) {
- Value value = values.get(i);
- if (value.numberOfPhiUsers() > 0) {
- continue;
- }
- ParameterUsage usage = collectParameterUsages(i, value);
- if (usage != null) {
- usages.add(usage);
- }
- }
- feedback.setParameterUsages(method, usages.isEmpty() ? null : new ParameterUsagesInfo(usages));
- }
-
- private ParameterUsage collectParameterUsages(int i, Value value) {
- ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
- for (Instruction user : value.uniqueUsers()) {
- if (!builder.note(user)) {
- return null;
- }
- }
- return builder.build();
- }
-
- // This method defines trivial instance initializer as follows:
- //
- // ** The initializer may call the initializer of the base class, which
- // itself must be trivial.
- //
- // ** java.lang.Object.<init>() is considered trivial.
- //
- // ** all arguments passed to a super-class initializer must be non-throwing
- // constants or arguments.
- //
- // ** Assigns arguments or non-throwing constants to fields of this class.
- //
- // (Note that this initializer does not have to have zero arguments.)
- private TrivialInitializer computeInstanceInitializerInfo(
- IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
- Value receiver = code.getThis();
-
- for (Instruction insn : code.instructions()) {
- if (insn.isReturn()) {
- continue;
- }
-
- if (insn.isArgument()) {
- continue;
- }
-
- if (insn.isConstInstruction()) {
- if (insn.instructionInstanceCanThrow()) {
- return null;
- } else {
- continue;
- }
- }
-
- if (insn.isInvokeDirect()) {
- InvokeDirect invokedDirect = insn.asInvokeDirect();
- DexMethod invokedMethod = invokedDirect.getInvokedMethod();
-
- if (invokedMethod.holder != clazz.superType) {
- return null;
- }
-
- // java.lang.Object.<init>() is considered trivial.
- if (invokedMethod == dexItemFactory.objectMethods.constructor) {
- continue;
- }
-
- DexClass holder = typeToClass.apply(invokedMethod.holder);
- if (holder == null) {
- return null;
- }
-
- DexEncodedMethod callTarget = holder.lookupDirectMethod(invokedMethod);
- if (callTarget == null ||
- !callTarget.isInstanceInitializer() ||
- callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null ||
- invokedDirect.getReceiver() != receiver) {
- return null;
- }
-
- for (Value value : invokedDirect.inValues()) {
- if (value != receiver && !(value.isConstant() || value.isArgument())) {
- return null;
- }
- }
- continue;
- }
-
- if (insn.isInstancePut()) {
- InstancePut instancePut = insn.asInstancePut();
- DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
- if (field == null ||
- instancePut.object() != receiver ||
- (instancePut.value() != receiver && !instancePut.value().isArgument())) {
- return null;
- }
- continue;
- }
-
- if (insn.isGoto()) {
- // Trivial goto to the next block.
- if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
- continue;
- }
- return null;
- }
-
- // Other instructions make the instance initializer not eligible.
- return null;
- }
-
- return TrivialInstanceInitializer.INSTANCE;
- }
-
- // This method defines trivial class initializer as follows:
- //
- // ** The initializer may only instantiate an instance of the same class,
- // initialize it with a call to a trivial constructor *without* arguments,
- // and assign this instance to a static final field of the same class.
- //
- private synchronized TrivialInitializer computeClassInitializerInfo(IRCode code, DexClass clazz) {
- Value createdSingletonInstance = null;
- DexField singletonField = null;
-
- for (Instruction insn : code.instructions()) {
- if (insn.isConstNumber()) {
- continue;
- }
-
- if (insn.isConstString()) {
- if (insn.instructionInstanceCanThrow()) {
- return null;
- }
- continue;
- }
-
- if (insn.isReturn()) {
- continue;
- }
-
- if (insn.isNewInstance()) {
- NewInstance newInstance = insn.asNewInstance();
- if (createdSingletonInstance != null ||
- newInstance.clazz != clazz.type ||
- insn.outValue() == null) {
- return null;
- }
- createdSingletonInstance = insn.outValue();
- continue;
- }
-
- if (insn.isInvokeDirect()) {
- InvokeDirect invokedDirect = insn.asInvokeDirect();
- if (createdSingletonInstance == null ||
- invokedDirect.getReceiver() != createdSingletonInstance) {
- return null;
- }
-
- DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
- if (callTarget == null ||
- !callTarget.isInstanceInitializer() ||
- !callTarget.method.proto.parameters.isEmpty() ||
- callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null) {
- return null;
- }
- continue;
- }
-
- if (insn.isStaticPut()) {
- StaticPut staticPut = insn.asStaticPut();
- if (singletonField != null
- || createdSingletonInstance == null
- || staticPut.value() != createdSingletonInstance) {
- return null;
- }
-
- DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
- if (field == null ||
- !field.accessFlags.isStatic() ||
- !field.accessFlags.isFinal()) {
- return null;
- }
- singletonField = field.field;
- continue;
- }
-
- // Other instructions make the class initializer not eligible.
- return null;
- }
-
- return singletonField == null ? null : new TrivialClassInitializer(singletonField);
- }
-
- /**
- * An enum used to classify instructions according to a particular effect that they produce.
- *
- * The "effect" of an instruction can be seen as a program state change (or semantic change) at
- * runtime execution. For example, an instruction could cause the initialization of a class,
- * change the value of a field, ... while other instructions do not.
- *
- * This classification also depends on the type of analysis that is using it. For instance, an
- * analysis can look for instructions that cause class initialization while another look for
- * instructions that check nullness of a particular object.
- *
- * On the other hand, some instructions may provide a non desired effect which is a signal for
- * the analysis to stop.
- */
- private enum InstructionEffect {
- DESIRED_EFFECT,
- CONDITIONAL_EFFECT,
- OTHER_EFFECT,
- NO_EFFECT
- }
-
- /**
- * Returns true if the given code unconditionally throws if value is null before any other side
- * effect instruction.
- *
- * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
- */
- public static boolean checksNullBeforeSideEffect(IRCode code, Value value, AppView<?> appView) {
- return alwaysTriggerExpectedEffectBeforeAnythingElse(
- code,
- (instr, it) -> {
- BasicBlock currentBlock = instr.getBlock();
- // If the code explicitly checks the nullability of the value, we should visit the next
- // block that corresponds to the null value where NPE semantic could be preserved.
- if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
- return InstructionEffect.CONDITIONAL_EFFECT;
- }
- 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
- == 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()) {
- if (isNullCheck(predecessor.exit(), value)) {
- return InstructionEffect.DESIRED_EFFECT;
- }
- }
- // Hitting here means that this call might be used for other parameters. If we don't
- // bail out, it will be regarded as side effects for the current value.
- return InstructionEffect.NO_EFFECT;
- } else {
- // Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
- assert invokedMethod.name
- == appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull.name;
- return InstructionEffect.DESIRED_EFFECT;
- }
- }
- if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
- it.next(); // Skip call to NullPointerException.<init>.
- return InstructionEffect.NO_EFFECT;
- } 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
- // catch the exception, even if it cannot catch a NullPointerException.
- if (!currentBlock.hasCatchHandlers()) {
- // We found a NPE check on the value.
- return InstructionEffect.DESIRED_EFFECT;
- }
- } else if (instr.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
- // If the current instruction is const-string, this could load the parameter name.
- // Just make sure it is indeed not throwing.
- if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
- return InstructionEffect.NO_EFFECT;
- }
- // We found a side effect before a NPE check.
- return InstructionEffect.OTHER_EFFECT;
- }
- return InstructionEffect.NO_EFFECT;
- });
- }
-
- // Note that this method may have false positives, since the application could in principle
- // declare a method called checkParameterIsNotNull(parameter, message) or
- // throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
- private static boolean isKotlinNullCheck(Instruction instr, Value value, AppView<?> appView) {
- if (!instr.isInvokeStatic()) {
- return false;
- }
- // We need to strip the holder, since Kotlin adds different versions of null-check machinery,
- // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
- MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
- Wrapper<DexMethod> checkParameterIsNotNull =
- wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull);
- Wrapper<DexMethod> throwParamIsNullException =
- wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException);
- DexMethod invokedMethod =
- 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))) {
- if (invokedMethod.holder.getPackageDescriptor().startsWith(Kotlin.NAME)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isNullCheck(Instruction instr, Value value) {
- return instr.isIf() && instr.asIf().isZeroTest()
- && instr.inValues().get(0).equals(value)
- && (instr.asIf().getType() == Type.EQ || instr.asIf().getType() == Type.NE);
- }
-
- /**
- * Returns true if the given instruction is {@code v <- new-instance NullPointerException}, and
- * the next instruction is {@code invoke-direct v, NullPointerException.<init>()}.
- */
- private static boolean isInstantiationOfNullPointerException(
- Instruction instruction, InstructionIterator it, DexItemFactory dexItemFactory) {
- if (!instruction.isNewInstance()
- || instruction.asNewInstance().clazz != dexItemFactory.npeType) {
- return false;
- }
- Instruction next = it.peekNext();
- if (next == null
- || !next.isInvokeDirect()
- || next.asInvokeDirect().getInvokedMethod() != dexItemFactory.npeMethods.init) {
- return false;
- }
- return true;
- }
-
- /**
- * Returns true if the given code unconditionally triggers class initialization before any other
- * side effecting instruction.
- *
- * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
- */
- private static boolean triggersClassInitializationBeforeSideEffect(
- DexType clazz, IRCode code, AppView<?> appView) {
- return alwaysTriggerExpectedEffectBeforeAnythingElse(
- code,
- (instruction, it) -> {
- DexType context = code.method.method.holder;
- if (instruction.definitelyTriggersClassInitialization(
- clazz, context, 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.
- // Note: this is a conservative approach where we consider that any catch handler could
- // catch the exception, even if it cannot catch an ExceptionInInitializerError.
- if (!instruction.getBlock().hasCatchHandlers()) {
- // We found an instruction that preserves initialization of the class.
- return InstructionEffect.DESIRED_EFFECT;
- }
- } else if (instruction.instructionMayHaveSideEffects(appView, clazz)) {
- // We found a side effect before class initialization.
- return InstructionEffect.OTHER_EFFECT;
- }
- return InstructionEffect.NO_EFFECT;
- });
- }
-
- /**
- * Returns true if the given code unconditionally triggers an expected effect before anything
- * else, false otherwise.
- *
- * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
- */
- private static boolean alwaysTriggerExpectedEffectBeforeAnythingElse(
- IRCode code, BiFunction<Instruction, InstructionIterator, InstructionEffect> function) {
- final int color = code.reserveMarkingColor();
- try {
- ArrayDeque<BasicBlock> worklist = new ArrayDeque<>();
- final BasicBlock entry = code.entryBlock();
- worklist.add(entry);
- entry.mark(color);
-
- while (!worklist.isEmpty()) {
- BasicBlock currentBlock = worklist.poll();
- assert currentBlock.isMarked(color);
-
- InstructionEffect result = InstructionEffect.NO_EFFECT;
- InstructionIterator it = currentBlock.iterator();
- while (result == InstructionEffect.NO_EFFECT && it.hasNext()) {
- result = function.apply(it.next(), it);
- }
- if (result == InstructionEffect.OTHER_EFFECT) {
- // We found an instruction that is causing an unexpected side effect.
- return false;
- } else if (result == InstructionEffect.DESIRED_EFFECT) {
- // The current path is causing the expected effect. No need to go deeper in this path,
- // go to the next block in the work list.
- continue;
- } else if (result == InstructionEffect.CONDITIONAL_EFFECT) {
- assert !currentBlock.getNormalSuccessors().isEmpty();
- Instruction lastInstruction = currentBlock.getInstructions().getLast();
- assert lastInstruction.isIf();
- // The current path is checking if the value of interest is null. Go deeper into the path
- // that corresponds to the null value.
- BasicBlock targetIfReceiverIsNull = lastInstruction.asIf().targetFromCondition(0);
- if (!targetIfReceiverIsNull.isMarked(color)) {
- worklist.add(targetIfReceiverIsNull);
- targetIfReceiverIsNull.mark(color);
- }
- } else {
- assert result == InstructionEffect.NO_EFFECT;
- // The block did not cause any particular effect.
- if (currentBlock.getNormalSuccessors().isEmpty()) {
- // This is the end of the current non-exceptional path and we did not find any expected
- // effect. It means there is at least one path where the expected effect does not
- // happen.
- Instruction lastInstruction = currentBlock.getInstructions().getLast();
- assert lastInstruction.isReturn() || lastInstruction.isThrow();
- return false;
- } else {
- // Look into successors
- for (BasicBlock successor : currentBlock.getSuccessors()) {
- if (!successor.isMarked(color)) {
- worklist.add(successor);
- successor.mark(color);
- }
- }
- }
- }
- }
-
- // If we reach this point, we checked that the expected effect happens in every possible path.
- return true;
- } finally {
- code.returnMarkingColor(color);
- }
- }
-
private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
// TODO(sgjesse): Insert cast if required.
TypeLatticeElement returnType =
@@ -2197,11 +1573,6 @@
}
}
- private boolean canBeFolded(Instruction instruction) {
- return (instruction.isBinop() && instruction.asBinop().canBeFolded()) ||
- (instruction.isUnop() && instruction.asUnop().canBeFolded());
- }
-
// Split constants that flow into ranged invokes. This gives the register allocator more
// freedom in assigning register to ranged invokes which can greatly reduce the number
// of register needed (and thereby code size as well).
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 263da6c..e0d77fb 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
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
+import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
@@ -21,6 +24,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
@@ -264,12 +268,6 @@
return false;
}
- // Don't inline if target is synchronized.
- if (singleTarget.accessFlags.isSynchronized()) {
- whyAreYouNotInliningReporter.reportSynchronizedMethod();
- return false;
- }
-
if (reason == Reason.DUAL_CALLER) {
if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
// When we have a method with two call sites, we simply inline the method as we normally do
@@ -552,58 +550,129 @@
@Override
public boolean willExceedBudget(
+ IRCode code,
+ InvokeMethod invoke,
InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (inlinee.reason.mustBeInlined()) {
return false;
}
+ return willExceedInstructionBudget(inlinee, whyAreYouNotInliningReporter)
+ || willExceedMonitorEnterValuesBudget(code, invoke, inlinee, whyAreYouNotInliningReporter)
+ || willExceedControlFlowResolutionBlocksBudget(
+ inlinee, block, whyAreYouNotInliningReporter);
+ }
- if (block.hasCatchHandlers() && inlinee.reason != Reason.FORCE) {
- // Inlining could lead to an explosion of move-exception and resolution moves. As an
- // example, consider the following piece of code.
- // try {
- // ...
- // foo();
- // ...
- // } catch (A e) { ... }
- // } catch (B e) { ... }
- // } catch (C e) { ... }
- //
- // The generated code for the above example will have a move-exception instruction
- // for each of the three catch handlers. Furthermore, the blocks with these move-
- // exception instructions may require a number of resolution moves to setup the
- // register state for the catch handlers. When inlining foo(), the generated code
- // will have a move-exception instruction *for each of the instructions in foo()
- // that can throw*, along with the necessary resolution moves for each exception-
- // edge. We therefore abort inlining if the number of exception-edges explode.
- int numberOfThrowingInstructionsInInlinee = 0;
- for (BasicBlock inlineeBlock : inlinee.code.blocks) {
- numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions();
- }
- // Estimate the number of "control flow resolution blocks", where we will insert a
- // move-exception instruction (if needed), along with all the resolution moves that
- // will be needed to setup the register state for the catch handler.
- int estimatedNumberOfControlFlowResolutionBlocks =
- numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
- // Abort if inlining could lead to an explosion in the number of control flow
- // resolution blocks that setup the register state before the actual catch handler.
- int threshold = appView.options().inliningControlFlowResolutionBlocksThreshold;
- if (estimatedNumberOfControlFlowResolutionBlocks >= threshold) {
- whyAreYouNotInliningReporter
- .reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
- estimatedNumberOfControlFlowResolutionBlocks, threshold);
- return true;
- }
- }
-
+ private boolean willExceedInstructionBudget(
+ InlineeWithReason inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
int numberOfInstructions = Inliner.numberOfInstructions(inlinee.code);
if (instructionAllowance < Inliner.numberOfInstructions(inlinee.code)) {
whyAreYouNotInliningReporter.reportWillExceedInstructionBudget(
numberOfInstructions, instructionAllowance);
return true;
}
+ return false;
+ }
+ /**
+ * If inlining would lead to additional lock values in the caller, then check that the number of
+ * lock values after inlining would not exceed the threshold.
+ *
+ * <p>The motivation for limiting the number of locks in a given method is that the register
+ * allocator will attempt to pin a register for each lock value. Thus, if a method has many locks,
+ * many registers will be pinned, which will lead to high register pressure.
+ */
+ private boolean willExceedMonitorEnterValuesBudget(
+ IRCode code,
+ InvokeMethod invoke,
+ InlineeWithReason inlinee,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ if (!code.metadata().mayHaveMonitorInstruction()) {
+ return false;
+ }
+
+ if (!inlinee.code.metadata().mayHaveMonitorInstruction()) {
+ return false;
+ }
+
+ Set<DexType> constantMonitorEnterValues = Sets.newIdentityHashSet();
+ Set<Value> nonConstantMonitorEnterValues = Sets.newIdentityHashSet();
+ collectAllMonitorEnterValues(code, constantMonitorEnterValues, nonConstantMonitorEnterValues);
+ if (constantMonitorEnterValues.isEmpty() && nonConstantMonitorEnterValues.isEmpty()) {
+ return false;
+ }
+
+ for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) {
+ Value monitorEnterValue = monitor.object().getAliasedValue();
+ if (monitorEnterValue.isArgument()) {
+ monitorEnterValue =
+ invoke
+ .arguments()
+ .get(monitorEnterValue.computeArgumentPosition(inlinee.code))
+ .getAliasedValue();
+ }
+ addMonitorEnterValue(
+ monitorEnterValue, constantMonitorEnterValues, nonConstantMonitorEnterValues);
+ }
+
+ int numberOfMonitorEnterValuesAfterInlining =
+ constantMonitorEnterValues.size() + nonConstantMonitorEnterValues.size();
+ int threshold = appView.options().inliningMonitorEnterValuesAllowance;
+ if (numberOfMonitorEnterValuesAfterInlining > threshold) {
+ whyAreYouNotInliningReporter.reportWillExceedMonitorEnterValuesBudget(
+ numberOfMonitorEnterValuesAfterInlining, threshold);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Inlining could lead to an explosion of move-exception and resolution moves. As an example,
+ * consider the following piece of code.
+ *
+ * <pre>
+ * try {
+ * ...
+ * foo();
+ * ...
+ * } catch (A e) { ... }
+ * } catch (B e) { ... }
+ * } catch (C e) { ... }
+ * </pre>
+ *
+ * <p>The generated code for the above example will have a move-exception instruction for each of
+ * the three catch handlers. Furthermore, the blocks with these move-exception instructions may
+ * require a number of resolution moves to setup the register state for the catch handlers. When
+ * inlining foo(), the generated code will have a move-exception instruction *for each of the
+ * instructions in foo() that can throw*, along with the necessary resolution moves for each
+ * exception-edge. We therefore abort inlining if the number of exception-edges explode.
+ */
+ private boolean willExceedControlFlowResolutionBlocksBudget(
+ InlineeWithReason inlinee,
+ BasicBlock block,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ if (!block.hasCatchHandlers()) {
+ return false;
+ }
+ int numberOfThrowingInstructionsInInlinee = 0;
+ for (BasicBlock inlineeBlock : inlinee.code.blocks) {
+ numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions();
+ }
+ // Estimate the number of "control flow resolution blocks", where we will insert a
+ // move-exception instruction (if needed), along with all the resolution moves that
+ // will be needed to setup the register state for the catch handler.
+ int estimatedNumberOfControlFlowResolutionBlocks =
+ numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
+ // Abort if inlining could lead to an explosion in the number of control flow
+ // resolution blocks that setup the register state before the actual catch handler.
+ int threshold = appView.options().inliningControlFlowResolutionBlocksThreshold;
+ if (estimatedNumberOfControlFlowResolutionBlocks >= threshold) {
+ whyAreYouNotInliningReporter.reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
+ estimatedNumberOfControlFlowResolutionBlocks, threshold);
+ return true;
+ }
return false;
}
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 cbaa36f..fb1ef58 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
@@ -97,6 +97,8 @@
@Override
public boolean willExceedBudget(
+ IRCode code,
+ InvokeMethod invoke,
InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
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 44c72c5..fef9538 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
@@ -3,20 +3,27 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.google.common.base.Predicates.not;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
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.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
@@ -24,6 +31,9 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.MoveException;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
@@ -41,6 +51,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -575,14 +586,26 @@
AppView<? extends AppInfoWithSubtyping> appView,
Position callerPosition,
LensCodeRewriter lensCodeRewriter) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ InternalOptions options = appView.options();
Origin origin = appView.appInfo().originFor(target.method.holder);
// Build the IR for a yet not processed method, and perform minimal IR processing.
IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
- // Insert a null check if this is needed to preserve the implicit null check for the
- // receiver.
- if (shouldSynthesizeNullCheckForReceiver) {
+ // Insert a null check if this is needed to preserve the implicit null check for the receiver.
+ // This is only needed if we do not also insert a monitor-enter instruction, since that will
+ // throw a NPE if the receiver is null.
+ //
+ // Note: When generating DEX, we synthesize monitor-enter/exit instructions during IR
+ // building, and therefore, we do not need to do anything here. Upon writing, we will use the
+ // flag "declared synchronized" instead of "synchronized".
+ boolean shouldSynthesizeMonitorEnterExit =
+ target.accessFlags.isSynchronized() && options.isGeneratingClassFiles();
+ boolean isSynthesizingNullCheckForReceiverUsingMonitorEnter =
+ shouldSynthesizeMonitorEnterExit && !target.isStatic();
+ if (shouldSynthesizeNullCheckForReceiver
+ && !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
List<Value> arguments = code.collectArguments();
if (!arguments.isEmpty()) {
Value receiver = arguments.get(0);
@@ -617,9 +640,119 @@
assert false : "Unable to synthesize a null check for the receiver";
}
}
+
+ // Insert monitor-enter and monitor-exit instructions if the method is synchronized.
+ if (shouldSynthesizeMonitorEnterExit) {
+ TypeLatticeElement throwableType =
+ TypeLatticeElement.fromDexType(
+ dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView);
+
+ code.prepareBlocksForCatchHandlers();
+
+ int nextBlockNumber = code.getHighestBlockNumber() + 1;
+
+ // Create a block for holding the monitor-exit instruction.
+ BasicBlock monitorExitBlock = new BasicBlock();
+ monitorExitBlock.setNumber(nextBlockNumber++);
+
+ // For each block in the code that may throw, add a catch-all handler targeting the
+ // monitor-exit block.
+ List<BasicBlock> moveExceptionBlocks = new ArrayList<>();
+ for (BasicBlock block : code.blocks) {
+ if (!block.canThrow()) {
+ continue;
+ }
+ if (block.hasCatchHandlers()
+ && block.getCatchHandlersWithSuccessorIndexes().hasCatchAll(dexItemFactory)) {
+ continue;
+ }
+ BasicBlock moveExceptionBlock =
+ BasicBlock.createGotoBlock(
+ nextBlockNumber++, Position.none(), code.metadata(), monitorExitBlock);
+ InstructionListIterator moveExceptionBlockIterator =
+ moveExceptionBlock.listIterator(code);
+ moveExceptionBlockIterator.setInsertionPosition(Position.syntheticNone());
+ moveExceptionBlockIterator.add(
+ new MoveException(
+ code.createValue(throwableType), dexItemFactory.throwableType, options));
+ block.appendCatchHandler(moveExceptionBlock, dexItemFactory.throwableType);
+ moveExceptionBlocks.add(moveExceptionBlock);
+ }
+
+ // Create a phi for the exception values such that we can rethrow the exception if needed.
+ Value exceptionValue;
+ if (moveExceptionBlocks.size() == 1) {
+ exceptionValue =
+ ListUtils.first(moveExceptionBlocks).getInstructions().getFirst().outValue();
+ } else {
+ Phi phi = code.createPhi(monitorExitBlock, throwableType);
+ List<Value> operands =
+ ListUtils.map(
+ moveExceptionBlocks, block -> block.getInstructions().getFirst().outValue());
+ phi.addOperands(operands);
+ exceptionValue = phi;
+ }
+
+ InstructionListIterator monitorExitBlockIterator = monitorExitBlock.listIterator(code);
+ monitorExitBlockIterator.setInsertionPosition(Position.syntheticNone());
+ monitorExitBlockIterator.add(new Throw(exceptionValue));
+ monitorExitBlock.getMutablePredecessors().addAll(moveExceptionBlocks);
+
+ // Insert the newly created blocks.
+ code.blocks.addAll(moveExceptionBlocks);
+ code.blocks.add(monitorExitBlock);
+
+ // Create a block for holding the monitor-enter instruction. Note that, since this block
+ // is created after we attach catch-all handlers to the code, this block will not have any
+ // catch handlers.
+ BasicBlock entryBlock = code.entryBlock();
+ InstructionListIterator entryBlockIterator = entryBlock.listIterator(code);
+ entryBlockIterator.nextUntil(not(Instruction::isArgument));
+ entryBlockIterator.previous();
+ BasicBlock monitorEnterBlock = entryBlockIterator.split(code, 0, null);
+ assert !monitorEnterBlock.hasCatchHandlers();
+
+ InstructionListIterator monitorEnterBlockIterator = monitorEnterBlock.listIterator(code);
+ monitorEnterBlockIterator.setInsertionPosition(Position.syntheticNone());
+
+ // If this is a static method, then the class object will act as the lock, so we load it
+ // using a const-class instruction.
+ Value lockValue;
+ if (target.isStatic()) {
+ lockValue =
+ code.createValue(
+ TypeLatticeElement.fromDexType(
+ dexItemFactory.objectType, definitelyNotNull(), appView));
+ monitorEnterBlockIterator.add(new ConstClass(lockValue, target.method.holder));
+ } else {
+ lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
+ }
+
+ // Insert the monitor-enter and monitor-exit instructions.
+ monitorEnterBlockIterator.add(new Monitor(Monitor.Type.ENTER, lockValue));
+ monitorExitBlockIterator.previous();
+ monitorExitBlockIterator.add(new Monitor(Monitor.Type.EXIT, lockValue));
+ monitorExitBlock.close(null);
+
+ for (BasicBlock block : code.blocks) {
+ if (block.exit().isReturn()) {
+ // Since return instructions are not allowed after a throwing instruction in a block
+ // with catch handlers, the call to prepareBlocksForCatchHandlers() has already taken
+ // care of ensuring that all return blocks have no throwing instructions.
+ assert !block.canThrow();
+
+ InstructionListIterator instructionIterator =
+ block.listIterator(code, block.getInstructions().size() - 1);
+ instructionIterator.setInsertionPosition(Position.syntheticNone());
+ instructionIterator.add(new Monitor(Monitor.Type.EXIT, lockValue));
+ }
+ }
+ }
+
if (!target.isProcessed()) {
lensCodeRewriter.rewrite(code, target);
}
+ assert code.isConsistentSSA();
return new InlineeWithReason(code, reason);
}
}
@@ -778,7 +911,8 @@
appView,
getPositionForInlining(invoke, context),
lensCodeRewriter);
- if (strategy.willExceedBudget(inlinee, block, whyAreYouNotInliningReporter)) {
+ if (strategy.willExceedBudget(
+ code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 074160b..f1256d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -208,8 +208,7 @@
}
public ConstraintWithTarget forMonitor() {
- // Conservative choice.
- return ConstraintWithTarget.NEVER;
+ return ConstraintWithTarget.ALWAYS;
}
public ConstraintWithTarget forMove() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 21b060a..5f9bb2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -30,6 +30,8 @@
* <p>Return true if the strategy will *not* allow inlining.
*/
boolean willExceedBudget(
+ IRCode code,
+ InvokeMethod invoke,
InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 0adf3f7..cea57a8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -22,7 +22,6 @@
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -31,14 +30,11 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
-import java.util.ArrayDeque;
import java.util.BitSet;
-import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
@@ -413,107 +409,4 @@
&& type.asReferenceTypeLatticeElement().isNullable()
&& value.numberOfAllUsers() > 0;
}
-
- public void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
- Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
- normalExits.addAll(code.computeNormalExitBlocks());
- DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- List<Value> arguments = code.collectArguments();
- BitSet facts = new BitSet();
- Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
- for (int index = 0; index < arguments.size(); index++) {
- Value argument = arguments.get(index);
- // Consider reference-type parameter only.
- if (!argument.getTypeLattice().isReference()) {
- continue;
- }
- // The receiver is always non-null on normal exits.
- if (argument.isThis()) {
- facts.set(index);
- continue;
- }
- // Collect basic blocks that check nullability of the parameter.
- nullCheckedBlocks.clear();
- for (Instruction user : argument.uniqueUsers()) {
- if (user.isAssumeNonNull()) {
- nullCheckedBlocks.add(user.asAssumeNonNull().getBlock());
- }
- if (user.isIf()
- && user.asIf().isZeroTest()
- && (user.asIf().getType() == Type.EQ || user.asIf().getType() == Type.NE)) {
- nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
- }
- }
- if (!nullCheckedBlocks.isEmpty()) {
- boolean allExitsCovered = true;
- for (BasicBlock normalExit : normalExits) {
- if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
- allExitsCovered = false;
- break;
- }
- }
- if (allExitsCovered) {
- facts.set(index);
- }
- }
- }
- if (facts.length() > 0) {
- feedback.setNonNullParamOnNormalExits(code.method, facts);
- }
- }
-
- private boolean isNormalExitDominated(
- BasicBlock normalExit,
- IRCode code,
- DominatorTree dominatorTree,
- Set<BasicBlock> nullCheckedBlocks) {
- // Each normal exit should be...
- for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
- // A) ...directly dominated by any null-checked block.
- if (dominatorTree.dominatedBy(normalExit, nullCheckedBlock)) {
- return true;
- }
- }
- // B) ...or indirectly dominated by null-checked blocks.
- // Although the normal exit is not dominated by any of null-checked blocks (because of other
- // paths to the exit), it could be still the case that all possible paths to that exit should
- // pass some of null-checked blocks.
- Set<BasicBlock> visited = Sets.newIdentityHashSet();
- // Initial fan-out of predecessors.
- Deque<BasicBlock> uncoveredPaths = new ArrayDeque<>(normalExit.getPredecessors());
- while (!uncoveredPaths.isEmpty()) {
- BasicBlock uncoveredPath = uncoveredPaths.poll();
- // Stop traversing upwards if we hit the entry block: if the entry block has an non-null,
- // this case should be handled already by A) because the entry block surely dominates all
- // normal exits.
- if (uncoveredPath == code.entryBlock()) {
- return false;
- }
- // Make sure we're not visiting the same block over and over again.
- if (!visited.add(uncoveredPath)) {
- // But, if that block is the last one in the queue, the normal exit is not fully covered.
- if (uncoveredPaths.isEmpty()) {
- return false;
- } else {
- continue;
- }
- }
- boolean pathCovered = false;
- for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
- if (dominatorTree.dominatedBy(uncoveredPath, nullCheckedBlock)) {
- pathCovered = true;
- break;
- }
- }
- if (!pathCovered) {
- // Fan out predecessors one more level.
- // Note that remaining, unmatched null-checked blocks should cover newly added paths.
- uncoveredPaths.addAll(uncoveredPath.getPredecessors());
- }
- }
- // Reaching here means that every path to the given normal exit is covered by the set of
- // null-checked blocks.
- assert uncoveredPaths.isEmpty();
- return true;
- }
}
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 f836195..809db6e 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
@@ -494,6 +494,13 @@
continue;
}
+ if (user.isMonitor()) {
+ // Since this instance never escapes and is guaranteed to be non-null, any monitor
+ // instructions are no-ops.
+ removeInstruction(user);
+ continue;
+ }
+
throw new Unreachable(
"Unexpected usage left in method `"
+ method.method.toSourceString()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 3279d51..9283234 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -37,8 +37,8 @@
static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null;
static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
static boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false;
- private static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
- private static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
+ static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
+ static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
private DefaultMethodOptimizationInfo() {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
new file mode 100644
index 0000000..78369e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -0,0 +1,948 @@
+// 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.info;
+
+import static com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query.DIRECTLY;
+import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
+import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
+import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
+import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+public class MethodOptimizationInfoCollector {
+ private final AppView<?> appView;
+ private final InternalOptions options;
+ private final DexItemFactory dexItemFactory;
+
+ public MethodOptimizationInfoCollector(AppView<?> appView) {
+ this.appView = appView;
+ this.options = appView.options();
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ // TODO(b/141656615): Use this and then make all utils in this collector `private`.
+ public void collectMethodOptimizationInfo(
+ DexEncodedMethod method,
+ IRCode code,
+ OptimizationFeedback feedback,
+ DynamicTypeOptimization dynamicTypeOptimization) {
+ identifyClassInlinerEligibility(method, code, feedback);
+ identifyParameterUsages(method, code, feedback);
+ identifyReturnsArgument(method, code, feedback);
+ identifyTrivialInitializer(method, code, feedback);
+ if (options.enableInlining) {
+ identifyInvokeSemanticsForInlining(method, code, appView, feedback);
+ }
+ computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
+ computeInitializedClassesOnNormalExit(feedback, method, code);
+ computeMayHaveSideEffects(feedback, method, code);
+ computeReturnValueOnlyDependsOnArguments(feedback, method, code);
+ computeNonNullParamOrThrow(feedback, method, code);
+ computeNonNullParamOnNormalExits(feedback, code);
+ }
+
+ public void identifyClassInlinerEligibility(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ // Method eligibility is calculated in similar way for regular method
+ // and for the constructor. To be eligible method should only be using its
+ // receiver in the following ways:
+ //
+ // (1) as a receiver of reads/writes of instance fields of the holder class,
+ // (2) as a return value,
+ // (3) as a receiver of a call to the superclass initializer. Note that we don't
+ // check what is passed to superclass initializer as arguments, only check
+ // that it is not the instance being initialized,
+ // (4) as argument to a monitor instruction.
+ //
+ // Note that (4) can safely be removed as the receiver is guaranteed not to escape when we class
+ // inline it, and hence any monitor instructions are no-ops.
+ boolean instanceInitializer = method.isInstanceInitializer();
+ if (method.accessFlags.isNative()
+ || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)) {
+ return;
+ }
+
+ feedback.setClassInlinerEligibility(method, null); // To allow returns below.
+
+ Value receiver = code.getThis();
+ if (receiver.numberOfPhiUsers() > 0) {
+ return;
+ }
+
+ DexClass clazz = appView.definitionFor(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+
+ boolean receiverUsedAsReturnValue = false;
+ boolean seenSuperInitCall = false;
+ for (Instruction insn : receiver.uniqueUsers()) {
+ if (insn.isMonitor()) {
+ continue;
+ }
+
+ if (insn.isReturn()) {
+ receiverUsedAsReturnValue = true;
+ continue;
+ }
+
+ if (insn.isInstanceGet() || insn.isInstancePut()) {
+ if (insn.isInstancePut()) {
+ InstancePut instancePutInstruction = insn.asInstancePut();
+ // Only allow field writes to the receiver.
+ if (instancePutInstruction.object() != receiver) {
+ return;
+ }
+ // Do not allow the receiver to escape via a field write.
+ if (instancePutInstruction.value() == receiver) {
+ return;
+ }
+ }
+ DexField field = insn.asFieldInstruction().getField();
+ if (field.holder == clazz.type && clazz.lookupInstanceField(field) != null) {
+ // Require only accessing instance fields of the *current* class.
+ continue;
+ }
+ return;
+ }
+
+ // If this is an instance initializer allow one call to superclass instance initializer.
+ if (insn.isInvokeDirect()) {
+ InvokeDirect invokedDirect = insn.asInvokeDirect();
+ DexMethod invokedMethod = invokedDirect.getInvokedMethod();
+ if (dexItemFactory.isConstructor(invokedMethod)
+ && invokedMethod.holder == clazz.superType
+ && invokedDirect.inValues().lastIndexOf(receiver) == 0
+ && !seenSuperInitCall
+ && instanceInitializer) {
+ seenSuperInitCall = true;
+ continue;
+ }
+ // We don't support other direct calls yet.
+ return;
+ }
+
+ // Other receiver usages make the method not eligible.
+ return;
+ }
+ if (instanceInitializer && !seenSuperInitCall) {
+ // Call to super constructor not found?
+ return;
+ }
+
+ feedback.setClassInlinerEligibility(
+ method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
+ }
+
+ public void identifyParameterUsages(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ List<ParameterUsage> usages = new ArrayList<>();
+ List<Value> values = code.collectArguments();
+ for (int i = 0; i < values.size(); i++) {
+ Value value = values.get(i);
+ if (value.numberOfPhiUsers() > 0) {
+ continue;
+ }
+ ParameterUsage usage = collectParameterUsages(i, value);
+ if (usage != null) {
+ usages.add(usage);
+ }
+ }
+ feedback.setParameterUsages(method, usages.isEmpty() ? null : new ParameterUsagesInfo(usages));
+ }
+
+ private ParameterUsage collectParameterUsages(int i, Value value) {
+ ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
+ for (Instruction user : value.uniqueUsers()) {
+ if (!builder.note(user)) {
+ return null;
+ }
+ }
+ return builder.build();
+ }
+
+ public void identifyReturnsArgument(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ List<BasicBlock> normalExits = code.computeNormalExitBlocks();
+ if (normalExits.isEmpty()) {
+ feedback.methodNeverReturnsNormally(method);
+ return;
+ }
+ Return firstExit = normalExits.get(0).exit().asReturn();
+ if (firstExit.isReturnVoid()) {
+ return;
+ }
+ Value returnValue = firstExit.returnValue();
+ boolean isNeverNull = returnValue.getTypeLattice().isReference() && returnValue.isNeverNull();
+ for (int i = 1; i < normalExits.size(); i++) {
+ Return exit = normalExits.get(i).exit().asReturn();
+ Value value = exit.returnValue();
+ if (value != returnValue) {
+ returnValue = null;
+ }
+ isNeverNull &= value.getTypeLattice().isReference() && value.isNeverNull();
+ }
+ if (returnValue != null) {
+ if (returnValue.isArgument()) {
+ // Find the argument number.
+ int index = code.collectArguments().indexOf(returnValue);
+ assert index != -1;
+ feedback.methodReturnsArgument(method, index);
+ }
+ if (returnValue.isConstant()) {
+ if (returnValue.definition.isConstNumber()) {
+ long value = returnValue.definition.asConstNumber().getRawValue();
+ feedback.methodReturnsConstantNumber(method, value);
+ } else if (returnValue.definition.isConstString()) {
+ ConstString constStringInstruction = returnValue.definition.asConstString();
+ if (!constStringInstruction.instructionInstanceCanThrow()) {
+ feedback.methodReturnsConstantString(method, constStringInstruction.getValue());
+ }
+ }
+ }
+ }
+ if (isNeverNull) {
+ feedback.methodNeverReturnsNull(method);
+ }
+ }
+
+ public void identifyTrivialInitializer(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ if (!method.isInstanceInitializer() && !method.isClassInitializer()) {
+ return;
+ }
+
+ if (method.accessFlags.isNative()) {
+ return;
+ }
+
+ DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+
+ feedback.setTrivialInitializer(
+ method,
+ method.isInstanceInitializer()
+ ? computeInstanceInitializerInfo(code, clazz, appView::definitionFor)
+ : computeClassInitializerInfo(code, clazz));
+ }
+
+ // This method defines trivial class initializer as follows:
+ //
+ // ** The initializer may only instantiate an instance of the same class,
+ // initialize it with a call to a trivial constructor *without* arguments,
+ // and assign this instance to a static final field of the same class.
+ //
+ private synchronized TrivialInitializer computeClassInitializerInfo(IRCode code, DexClass clazz) {
+ Value createdSingletonInstance = null;
+ DexField singletonField = null;
+ for (Instruction insn : code.instructions()) {
+ if (insn.isConstNumber()) {
+ continue;
+ }
+
+ if (insn.isConstString()) {
+ if (insn.instructionInstanceCanThrow()) {
+ return null;
+ }
+ continue;
+ }
+
+ if (insn.isReturn()) {
+ continue;
+ }
+
+ if (insn.isNewInstance()) {
+ NewInstance newInstance = insn.asNewInstance();
+ if (createdSingletonInstance != null
+ || newInstance.clazz != clazz.type
+ || insn.outValue() == null) {
+ return null;
+ }
+ createdSingletonInstance = insn.outValue();
+ continue;
+ }
+
+ if (insn.isInvokeDirect()) {
+ InvokeDirect invokedDirect = insn.asInvokeDirect();
+ if (createdSingletonInstance == null
+ || invokedDirect.getReceiver() != createdSingletonInstance) {
+ return null;
+ }
+ DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
+ if (callTarget == null
+ || !callTarget.isInstanceInitializer()
+ || !callTarget.method.proto.parameters.isEmpty()
+ || callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null) {
+ return null;
+ }
+ continue;
+ }
+
+ if (insn.isStaticPut()) {
+ StaticPut staticPut = insn.asStaticPut();
+ if (singletonField != null
+ || createdSingletonInstance == null
+ || staticPut.value() != createdSingletonInstance) {
+ return null;
+ }
+ DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
+ if (field == null
+ || !field.accessFlags.isStatic()
+ || !field.accessFlags.isFinal()) {
+ return null;
+ }
+ singletonField = field.field;
+ continue;
+ }
+
+ // Other instructions make the class initializer not eligible.
+ return null;
+ }
+ return singletonField == null ? null : new TrivialClassInitializer(singletonField);
+ }
+
+ // This method defines trivial instance initializer as follows:
+ //
+ // ** The initializer may call the initializer of the base class, which
+ // itself must be trivial.
+ //
+ // ** java.lang.Object.<init>() is considered trivial.
+ //
+ // ** all arguments passed to a super-class initializer must be non-throwing
+ // constants or arguments.
+ //
+ // ** Assigns arguments or non-throwing constants to fields of this class.
+ //
+ // (Note that this initializer does not have to have zero arguments.)
+ private TrivialInitializer computeInstanceInitializerInfo(
+ IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
+ Value receiver = code.getThis();
+ for (Instruction insn : code.instructions()) {
+ if (insn.isReturn()) {
+ continue;
+ }
+
+ if (insn.isArgument()) {
+ continue;
+ }
+
+ if (insn.isConstInstruction()) {
+ if (insn.instructionInstanceCanThrow()) {
+ return null;
+ } else {
+ continue;
+ }
+ }
+
+ if (insn.isInvokeDirect()) {
+ InvokeDirect invokedDirect = insn.asInvokeDirect();
+ DexMethod invokedMethod = invokedDirect.getInvokedMethod();
+ if (invokedMethod.holder != clazz.superType) {
+ return null;
+ }
+ // java.lang.Object.<init>() is considered trivial.
+ if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+ continue;
+ }
+ DexClass holder = typeToClass.apply(invokedMethod.holder);
+ if (holder == null) {
+ return null;
+ }
+ DexEncodedMethod callTarget = holder.lookupDirectMethod(invokedMethod);
+ if (callTarget == null
+ || !callTarget.isInstanceInitializer()
+ || callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null
+ || invokedDirect.getReceiver() != receiver) {
+ return null;
+ }
+ for (Value value : invokedDirect.inValues()) {
+ if (value != receiver && !(value.isConstant() || value.isArgument())) {
+ return null;
+ }
+ }
+ continue;
+ }
+
+ if (insn.isInstancePut()) {
+ InstancePut instancePut = insn.asInstancePut();
+ DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
+ if (field == null
+ || instancePut.object() != receiver
+ || (instancePut.value() != receiver && !instancePut.value().isArgument())) {
+ return null;
+ }
+ continue;
+ }
+
+ if (insn.isGoto()) {
+ // Trivial goto to the next block.
+ if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
+ continue;
+ }
+ return null;
+ }
+
+ // Other instructions make the instance initializer not eligible.
+ return null;
+ }
+ return TrivialInstanceInitializer.INSTANCE;
+ }
+
+ public void identifyInvokeSemanticsForInlining(
+ DexEncodedMethod method, IRCode code, AppView<?> appView, OptimizationFeedback feedback) {
+ if (method.isStatic()) {
+ // Identifies if the method preserves class initialization after inlining.
+ 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, receiver, appView));
+ }
+ }
+
+ /**
+ * Returns true if the given code unconditionally triggers class initialization before any other
+ * side effecting instruction.
+ *
+ * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
+ */
+ private static boolean triggersClassInitializationBeforeSideEffect(
+ DexType clazz, IRCode code, AppView<?> appView) {
+ return alwaysTriggerExpectedEffectBeforeAnythingElse(
+ code,
+ (instruction, it) -> {
+ DexType context = code.method.method.holder;
+ if (instruction.definitelyTriggersClassInitialization(
+ clazz, context, 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.
+ // Note: this is a conservative approach where we consider that any catch handler could
+ // catch the exception, even if it cannot catch an ExceptionInInitializerError.
+ if (!instruction.getBlock().hasCatchHandlers()) {
+ // We found an instruction that preserves initialization of the class.
+ return InstructionEffect.DESIRED_EFFECT;
+ }
+ } else if (instruction.instructionMayHaveSideEffects(appView, clazz)) {
+ // We found a side effect before class initialization.
+ return InstructionEffect.OTHER_EFFECT;
+ }
+ return InstructionEffect.NO_EFFECT;
+ });
+ }
+
+ /**
+ * Returns true if the given code unconditionally triggers an expected effect before anything
+ * else, false otherwise.
+ *
+ * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
+ */
+ private static boolean alwaysTriggerExpectedEffectBeforeAnythingElse(
+ IRCode code, BiFunction<Instruction, InstructionIterator, InstructionEffect> function) {
+ final int color = code.reserveMarkingColor();
+ try {
+ ArrayDeque<BasicBlock> worklist = new ArrayDeque<>();
+ final BasicBlock entry = code.entryBlock();
+ worklist.add(entry);
+ entry.mark(color);
+
+ while (!worklist.isEmpty()) {
+ BasicBlock currentBlock = worklist.poll();
+ assert currentBlock.isMarked(color);
+
+ InstructionEffect result = InstructionEffect.NO_EFFECT;
+ InstructionIterator it = currentBlock.iterator();
+ while (result == InstructionEffect.NO_EFFECT && it.hasNext()) {
+ result = function.apply(it.next(), it);
+ }
+ if (result == InstructionEffect.OTHER_EFFECT) {
+ // We found an instruction that is causing an unexpected side effect.
+ return false;
+ } else if (result == InstructionEffect.DESIRED_EFFECT) {
+ // The current path is causing the expected effect. No need to go deeper in this path,
+ // go to the next block in the work list.
+ continue;
+ } else if (result == InstructionEffect.CONDITIONAL_EFFECT) {
+ assert !currentBlock.getNormalSuccessors().isEmpty();
+ Instruction lastInstruction = currentBlock.getInstructions().getLast();
+ assert lastInstruction.isIf();
+ // The current path is checking if the value of interest is null. Go deeper into the path
+ // that corresponds to the null value.
+ BasicBlock targetIfReceiverIsNull = lastInstruction.asIf().targetFromCondition(0);
+ if (!targetIfReceiverIsNull.isMarked(color)) {
+ worklist.add(targetIfReceiverIsNull);
+ targetIfReceiverIsNull.mark(color);
+ }
+ } else {
+ assert result == InstructionEffect.NO_EFFECT;
+ // The block did not cause any particular effect.
+ if (currentBlock.getNormalSuccessors().isEmpty()) {
+ // This is the end of the current non-exceptional path and we did not find any expected
+ // effect. It means there is at least one path where the expected effect does not
+ // happen.
+ Instruction lastInstruction = currentBlock.getInstructions().getLast();
+ assert lastInstruction.isReturn() || lastInstruction.isThrow();
+ return false;
+ } else {
+ // Look into successors
+ for (BasicBlock successor : currentBlock.getSuccessors()) {
+ if (!successor.isMarked(color)) {
+ worklist.add(successor);
+ successor.mark(color);
+ }
+ }
+ }
+ }
+ }
+ // If we reach this point, we checked that the expected effect happens in every possible path.
+ return true;
+ } finally {
+ code.returnMarkingColor(color);
+ }
+ }
+
+ /**
+ * Returns true if the given code unconditionally throws if value is null before any other side
+ * effect instruction.
+ *
+ * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
+ */
+ private static boolean checksNullBeforeSideEffect(IRCode code, Value value, AppView<?> appView) {
+ return alwaysTriggerExpectedEffectBeforeAnythingElse(
+ code,
+ (instr, it) -> {
+ BasicBlock currentBlock = instr.getBlock();
+ // If the code explicitly checks the nullability of the value, we should visit the next
+ // block that corresponds to the null value where NPE semantic could be preserved.
+ if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
+ return InstructionEffect.CONDITIONAL_EFFECT;
+ }
+ 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
+ == 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()) {
+ if (isNullCheck(predecessor.exit(), value)) {
+ return InstructionEffect.DESIRED_EFFECT;
+ }
+ }
+ // Hitting here means that this call might be used for other parameters. If we don't
+ // bail out, it will be regarded as side effects for the current value.
+ return InstructionEffect.NO_EFFECT;
+ } else {
+ // Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
+ assert invokedMethod.name
+ == appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull.name;
+ return InstructionEffect.DESIRED_EFFECT;
+ }
+ }
+ if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
+ it.next(); // Skip call to NullPointerException.<init>.
+ return InstructionEffect.NO_EFFECT;
+ } 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
+ // catch the exception, even if it cannot catch a NullPointerException.
+ if (!currentBlock.hasCatchHandlers()) {
+ // We found a NPE check on the value.
+ return InstructionEffect.DESIRED_EFFECT;
+ }
+ } else if (instr.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
+ // If the current instruction is const-string, this could load the parameter name.
+ // Just make sure it is indeed not throwing.
+ if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
+ return InstructionEffect.NO_EFFECT;
+ }
+ // We found a side effect before a NPE check.
+ return InstructionEffect.OTHER_EFFECT;
+ }
+ return InstructionEffect.NO_EFFECT;
+ });
+ }
+
+ /**
+ * An enum used to classify instructions according to a particular effect that they produce.
+ *
+ * The "effect" of an instruction can be seen as a program state change (or semantic change) at
+ * runtime execution. For example, an instruction could cause the initialization of a class,
+ * change the value of a field, ... while other instructions do not.
+ *
+ * This classification also depends on the type of analysis that is using it. For instance, an
+ * analysis can look for instructions that cause class initialization while another look for
+ * instructions that check nullness of a particular object.
+ *
+ * On the other hand, some instructions may provide a non desired effect which is a signal for
+ * the analysis to stop.
+ */
+ private enum InstructionEffect {
+ DESIRED_EFFECT,
+ CONDITIONAL_EFFECT,
+ OTHER_EFFECT,
+ NO_EFFECT
+ }
+
+ // Note that this method may have false positives, since the application could in principle
+ // declare a method called checkParameterIsNotNull(parameter, message) or
+ // throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
+ private static boolean isKotlinNullCheck(Instruction instr, Value value, AppView<?> appView) {
+ if (!instr.isInvokeStatic()) {
+ return false;
+ }
+ // We need to strip the holder, since Kotlin adds different versions of null-check machinery,
+ // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
+ MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
+ Wrapper<DexMethod> checkParameterIsNotNull =
+ wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull);
+ Wrapper<DexMethod> throwParamIsNullException =
+ wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException);
+ DexMethod invokedMethod =
+ 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))) {
+ if (invokedMethod.holder.getPackageDescriptor().startsWith(Kotlin.NAME)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isNullCheck(Instruction instr, Value value) {
+ return instr.isIf() && instr.asIf().isZeroTest()
+ && instr.inValues().get(0).equals(value)
+ && (instr.asIf().getType() == If.Type.EQ || instr.asIf().getType() == If.Type.NE);
+ }
+
+ /**
+ * Returns true if the given instruction is {@code v <- new-instance NullPointerException}, and
+ * the next instruction is {@code invoke-direct v, NullPointerException.<init>()}.
+ */
+ private static boolean isInstantiationOfNullPointerException(
+ Instruction instruction, InstructionIterator it, DexItemFactory dexItemFactory) {
+ if (!instruction.isNewInstance()
+ || instruction.asNewInstance().clazz != dexItemFactory.npeType) {
+ return false;
+ }
+ Instruction next = it.peekNext();
+ if (next == null
+ || !next.isInvokeDirect()
+ || next.asInvokeDirect().getInvokedMethod() != dexItemFactory.npeMethods.init) {
+ return false;
+ }
+ return true;
+ }
+
+ public void computeDynamicReturnType(
+ DynamicTypeOptimization dynamicTypeOptimization,
+ OptimizationFeedback feedback,
+ DexEncodedMethod method,
+ IRCode code) {
+ if (dynamicTypeOptimization != null) {
+ DexType staticReturnTypeRaw = method.method.proto.returnType;
+ if (!staticReturnTypeRaw.isReferenceType()) {
+ return;
+ }
+ TypeLatticeElement dynamicReturnType =
+ dynamicTypeOptimization.computeDynamicReturnType(method, code);
+ if (dynamicReturnType != null) {
+ TypeLatticeElement staticReturnType =
+ TypeLatticeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
+ // If the dynamic return type is not more precise than the static return type there is no
+ // need to record it.
+ if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) {
+ feedback.methodReturnsObjectOfType(method, appView, dynamicReturnType);
+ }
+ }
+ ClassTypeLatticeElement exactReturnType =
+ dynamicTypeOptimization.computeDynamicLowerBoundType(method, code);
+ if (exactReturnType != null) {
+ feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType);
+ }
+ }
+ }
+
+ public void computeInitializedClassesOnNormalExit(
+ OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+ if (options.enableInitializedClassesAnalysis && appView.appInfo().hasLiveness()) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ Set<DexType> initializedClasses =
+ InitializedClassesOnNormalExitAnalysis.computeInitializedClassesOnNormalExit(
+ appViewWithLiveness, code);
+ if (initializedClasses != null && !initializedClasses.isEmpty()) {
+ feedback.methodInitializesClassesOnNormalExit(method, initializedClasses);
+ }
+ }
+ }
+
+ public void computeMayHaveSideEffects(
+ OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+ // If the method is native, we don't know what could happen.
+ assert !method.accessFlags.isNative();
+ if (!options.enableSideEffectAnalysis) {
+ return;
+ }
+ if (appView.appInfo().withLiveness().mayHaveSideEffects.containsKey(method.method)) {
+ return;
+ }
+ DexType context = method.method.holder;
+ if (method.isClassInitializer()) {
+ // For class initializers, we also wish to compute if the class initializer has observable
+ // side effects.
+ ClassInitializerSideEffect classInitializerSideEffect =
+ ClassInitializerSideEffectAnalysis.classInitializerCanBePostponed(appView, code);
+ if (classInitializerSideEffect.isNone()) {
+ feedback.methodMayNotHaveSideEffects(method);
+ feedback.classInitializerMayBePostponed(method);
+ } else if (classInitializerSideEffect.canBePostponed()) {
+ feedback.classInitializerMayBePostponed(method);
+ }
+ return;
+ }
+ boolean mayHaveSideEffects;
+ if (method.accessFlags.isSynchronized()) {
+ // If the method is synchronized then it acquires a lock.
+ mayHaveSideEffects = true;
+ } else if (method.isInstanceInitializer() && hasNonTrivialFinalizeMethod(context)) {
+ // If a class T overrides java.lang.Object.finalize(), then treat the constructor as having
+ // side effects. This ensures that we won't remove instructions on the form `new-instance
+ // {v0}, T`.
+ mayHaveSideEffects = true;
+ } else {
+ // Otherwise, check if there is an instruction that has side effects.
+ mayHaveSideEffects =
+ Streams.stream(code.instructions())
+ .anyMatch(instruction -> instruction.instructionMayHaveSideEffects(appView, context));
+ }
+ if (!mayHaveSideEffects) {
+ feedback.methodMayNotHaveSideEffects(method);
+ }
+ }
+
+ // Returns true if `method` is an initializer and the enclosing class overrides the method
+ // `void java.lang.Object.finalize()`.
+ private boolean hasNonTrivialFinalizeMethod(DexType type) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz != null) {
+ if (clazz.isProgramClass() && !clazz.isInterface()) {
+ ResolutionResult resolutionResult =
+ appView
+ .appInfo()
+ .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMethods.finalize);
+ for (DexEncodedMethod target : resolutionResult.asListOfTargets()) {
+ if (target.method != appView.dexItemFactory().objectMethods.finalize) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ // Conservatively report that the library class could implement finalize().
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void computeReturnValueOnlyDependsOnArguments(
+ OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+ if (!options.enableDeterminismAnalysis) {
+ return;
+ }
+ boolean returnValueOnlyDependsOnArguments =
+ DeterminismAnalysis.returnValueOnlyDependsOnArguments(appView.withLiveness(), code);
+ if (returnValueOnlyDependsOnArguments) {
+ feedback.methodReturnValueOnlyDependsOnArguments(method);
+ }
+ }
+
+ // Track usage of parameters and compute their nullability and possibility of NPE.
+ public void computeNonNullParamOrThrow(
+ OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+ if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
+ return;
+ }
+ List<Value> arguments = code.collectArguments();
+ BitSet paramsCheckedForNull = new BitSet();
+ for (int index = 0; index < arguments.size(); index++) {
+ Value argument = arguments.get(index);
+ // This handles cases where the parameter is checked via Kotlin Intrinsics:
+ //
+ // kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
+ //
+ // or its inlined version:
+ //
+ // if (param != null) return;
+ // invoke-static throwParameterIsNullException(msg)
+ //
+ // or some other variants, e.g., throw null or NPE after the direct null check.
+ if (argument.isUsed() && checksNullBeforeSideEffect(code, argument, appView)) {
+ paramsCheckedForNull.set(index);
+ }
+ }
+ if (paramsCheckedForNull.length() > 0) {
+ feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
+ }
+ }
+
+ public void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
+ Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
+ normalExits.addAll(code.computeNormalExitBlocks());
+ DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+ List<Value> arguments = code.collectArguments();
+ BitSet facts = new BitSet();
+ Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+ for (int index = 0; index < arguments.size(); index++) {
+ Value argument = arguments.get(index);
+ // Consider reference-type parameter only.
+ if (!argument.getTypeLattice().isReference()) {
+ continue;
+ }
+ // The receiver is always non-null on normal exits.
+ if (argument.isThis()) {
+ facts.set(index);
+ continue;
+ }
+ // Collect basic blocks that check nullability of the parameter.
+ nullCheckedBlocks.clear();
+ for (Instruction user : argument.uniqueUsers()) {
+ if (user.isAssumeNonNull()) {
+ nullCheckedBlocks.add(user.asAssumeNonNull().getBlock());
+ }
+ if (user.isIf()
+ && user.asIf().isZeroTest()
+ && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
+ nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
+ }
+ }
+ if (!nullCheckedBlocks.isEmpty()) {
+ boolean allExitsCovered = true;
+ for (BasicBlock normalExit : normalExits) {
+ if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
+ allExitsCovered = false;
+ break;
+ }
+ }
+ if (allExitsCovered) {
+ facts.set(index);
+ }
+ }
+ }
+ if (facts.length() > 0) {
+ feedback.setNonNullParamOnNormalExits(code.method, facts);
+ }
+ }
+
+ private boolean isNormalExitDominated(
+ BasicBlock normalExit,
+ IRCode code,
+ DominatorTree dominatorTree,
+ Set<BasicBlock> nullCheckedBlocks) {
+ // Each normal exit should be...
+ for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
+ // A) ...directly dominated by any null-checked block.
+ if (dominatorTree.dominatedBy(normalExit, nullCheckedBlock)) {
+ return true;
+ }
+ }
+ // B) ...or indirectly dominated by null-checked blocks.
+ // Although the normal exit is not dominated by any of null-checked blocks (because of other
+ // paths to the exit), it could be still the case that all possible paths to that exit should
+ // pass some of null-checked blocks.
+ Set<BasicBlock> visited = Sets.newIdentityHashSet();
+ // Initial fan-out of predecessors.
+ Deque<BasicBlock> uncoveredPaths = new ArrayDeque<>(normalExit.getPredecessors());
+ while (!uncoveredPaths.isEmpty()) {
+ BasicBlock uncoveredPath = uncoveredPaths.poll();
+ // Stop traversing upwards if we hit the entry block: if the entry block has an non-null,
+ // this case should be handled already by A) because the entry block surely dominates all
+ // normal exits.
+ if (uncoveredPath == code.entryBlock()) {
+ return false;
+ }
+ // Make sure we're not visiting the same block over and over again.
+ if (!visited.add(uncoveredPath)) {
+ // But, if that block is the last one in the queue, the normal exit is not fully covered.
+ if (uncoveredPaths.isEmpty()) {
+ return false;
+ } else {
+ continue;
+ }
+ }
+ boolean pathCovered = false;
+ for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
+ if (dominatorTree.dominatedBy(uncoveredPath, nullCheckedBlock)) {
+ pathCovered = true;
+ break;
+ }
+ }
+ if (!pathCovered) {
+ // Fan out predecessors one more level.
+ // Note that remaining, unmatched null-checked blocks should cover newly added paths.
+ uncoveredPaths.addAll(uncoveredPath.getPredecessors());
+ }
+ }
+ // Reaching here means that every path to the given normal exit is covered by the set of
+ // null-checked blocks.
+ assert uncoveredPaths.isEmpty();
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 4a34157..06e7332 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -66,7 +66,8 @@
// invocation with null throwing code if an always-null argument is passed. Also used by Inliner
// to give a credit to null-safe code, e.g., Kotlin's null safe argument.
// Note that this bit set takes into account the receiver for instance methods.
- private BitSet nonNullParamOrThrow = null;
+ private BitSet nonNullParamOrThrow =
+ DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS;
// Stores information about nullability facts per parameter. If set, that means, the method
// somehow (e.g., null check, such as arg != null, or NPE-throwing instructions such as array
// access or another invocation) ensures the corresponding parameter is not null, and that is
@@ -74,7 +75,8 @@
// normally, the recorded parameter is definitely not null. These facts are used to propagate
// non-null information through {@link NonNullTracker}.
// Note that this bit set takes into account the receiver for instance methods.
- private BitSet nonNullParamOnNormalExits = null;
+ private BitSet nonNullParamOnNormalExits =
+ DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
private boolean reachabilitySensitive = false;
private boolean returnValueHasBeenPropagated = false;
@@ -435,4 +437,53 @@
assert this != DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
return new UpdatableMethodOptimizationInfo(this);
}
+
+ public void adjustOptimizationInfoAfterRemovingThisParameter() {
+ // cannotBeKept: doesn't depend on `this`
+ // classInitializerMayBePostponed: `this` could trigger <clinit> of the previous holder.
+ classInitializerMayBePostponed = false;
+ // hasBeenInlinedIntoSingleCallSite: then it should not be staticized.
+ hasBeenInlinedIntoSingleCallSite = false;
+ // initializedClassesOnNormalExit: `this` could trigger <clinit> of the previous holder.
+ initializedClassesOnNormalExit =
+ DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
+ // TODO(b/142401154): adjustable
+ returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT;
+ // mayHaveSideEffects: `this` Argument didn't have side effects, so removing it doesn't affect
+ // whether or not the method may have side effects.
+ // returnValueOnlyDependsOnArguments:
+ // if the method did before, so it does even after removing `this` Argument
+ // code is not changed, and thus the following *return* info is not changed either.
+ // * neverReturnsNull
+ // * neverReturnsNormally
+ // * returnsConstantNumber
+ // * returnedConstantNumber
+ // * returnsConstantString
+ // * returnedConstantString
+ // * returnsObjectOfType
+ // * returnsObjectWithLowerBoundType
+ // inlining: it is not inlined, and thus staticized. No more chance of inlining, though.
+ inlining = InlinePreference.Default;
+ // useIdentifierNameString: code is not changed.
+ // checksNullReceiverBeforeAnySideEffect: no more receiver.
+ checksNullReceiverBeforeAnySideEffect =
+ DefaultMethodOptimizationInfo.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
+ // triggersClassInitBeforeAnySideEffect: code is not changed.
+ triggersClassInitBeforeAnySideEffect =
+ DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
+ // classInlinerEligibility: chances are the method is not an instance method anymore.
+ classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
+ // trivialInitializerInfo: chances are the enclosing class is
+ trivialInitializerInfo = DefaultMethodOptimizationInfo.UNKNOWN_TRIVIAL_INITIALIZER;
+ // initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
+ initializerEnablingJavaAssertions =
+ DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
+ // TODO(b/142401154): adjustable
+ parametersUsages = DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
+ nonNullParamOrThrow = DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS;
+ nonNullParamOnNormalExits =
+ DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
+ // reachabilitySensitive: doesn't depend on `this`
+ // returnValueHasBeenPropagated: doesn't depend on `this`
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java
new file mode 100644
index 0000000..a29b465
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java
@@ -0,0 +1,39 @@
+// 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 com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class InlinerUtils {
+
+ public static void collectAllMonitorEnterValues(
+ IRCode code,
+ Set<DexType> constantMonitorEnterValues,
+ Set<Value> nonConstantMonitorEnterValues) {
+ assert code.metadata().mayHaveMonitorInstruction();
+ for (Monitor monitor : code.<Monitor>instructions(Instruction::isMonitorEnter)) {
+ Value monitorEnterValue = monitor.object().getAliasedValue();
+ addMonitorEnterValue(
+ monitorEnterValue, constantMonitorEnterValues, nonConstantMonitorEnterValues);
+ }
+ }
+
+ public static void addMonitorEnterValue(
+ Value monitorEnterValue,
+ Set<DexType> constantMonitorEnterValues,
+ Set<Value> nonConstantMonitorEnterValues) {
+ assert !monitorEnterValue.hasAliasedValue();
+ if (monitorEnterValue.isPhi() || !monitorEnterValue.definition.isConstClass()) {
+ nonConstantMonitorEnterValues.add(monitorEnterValue);
+ } else {
+ constantMonitorEnterValues.add(monitorEnterValue.definition.asConstClass().getValue());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index b5c96d4..467a5b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -104,9 +104,6 @@
public void reportRecursiveMethod() {}
@Override
- public void reportSynchronizedMethod() {}
-
- @Override
public void reportUnknownTarget() {}
@Override
@@ -122,6 +119,10 @@
public void reportWillExceedInstructionBudget(int numberOfInstructions, int threshold) {}
@Override
+ public void reportWillExceedMonitorEnterValuesBudget(
+ int numberOfMonitorEnterValuesAfterInlining, int threshold) {}
+
+ @Override
public boolean verifyReasonHasBeenReported() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 1e869eb..ab80544 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -101,8 +101,6 @@
public abstract void reportRecursiveMethod();
- public abstract void reportSynchronizedMethod();
-
abstract void reportUnknownTarget();
public abstract void reportUnsafeConstructorInliningDueToFinalFieldAssignment(
@@ -115,5 +113,8 @@
public abstract void reportWillExceedInstructionBudget(int numberOfInstructions, int threshold);
+ public abstract void reportWillExceedMonitorEnterValuesBudget(
+ int numberOfMonitorEnterValuesAfterInlining, int threshold);
+
public abstract boolean verifyReasonHasBeenReported();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index dcc122b..7d25661 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -210,11 +210,6 @@
}
@Override
- public void reportSynchronizedMethod() {
- print("synchronized methods are not inlined.");
- }
-
- @Override
public void reportUnknownTarget() {
print("could not find a single target.");
}
@@ -252,6 +247,16 @@
}
@Override
+ public void reportWillExceedMonitorEnterValuesBudget(
+ int numberOfMonitorEnterValuesAfterInlining, int threshold) {
+ printWithExceededThreshold(
+ "could negatively impact register allocation due to the number of monitor instructions",
+ "estimated number of locks after inlining",
+ numberOfMonitorEnterValuesAfterInlining,
+ threshold);
+ }
+
+ @Override
public boolean verifyReasonHasBeenReported() {
assert reasonHasBeenReported;
return true;
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 ad82955..89aeb2d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -225,6 +225,8 @@
public int inliningInstructionLimit = 3;
// This defines how many instructions of inlinees we can inlinee overall.
public int inliningInstructionAllowance = 1500;
+ // Maximum number of distinct values in a method that may be used in a monitor-enter instruction.
+ public int inliningMonitorEnterValuesAllowance = 4;
// Maximum number of control flow resolution blocks that setup the register state before
// the actual catch handler allowed when inlining. Threshold found empirically by testing on
// GMS Core.
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index b6cbe41..8e3fe1a 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -10,9 +10,44 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import java.util.Iterator;
import java.util.ListIterator;
+import java.util.NoSuchElementException;
import java.util.function.Predicate;
public class IteratorUtils {
+
+ public static <T, S extends T> Iterator<S> filter(Iterator<T> iterator, Predicate<T> predicate) {
+ return new Iterator<S>() {
+
+ private S next = advance();
+
+ @SuppressWarnings("unchecked")
+ private S advance() {
+ while (iterator.hasNext()) {
+ T element = iterator.next();
+ if (predicate.test(element)) {
+ return (S) element;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public S next() {
+ S current = next;
+ if (current == null) {
+ throw new NoSuchElementException();
+ }
+ next = advance();
+ return current;
+ }
+ };
+ }
+
public static <T> T peekPrevious(ListIterator<T> iterator) {
T previous = iterator.previous();
T next = iterator.next();
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 370d5e2..79f4f6c 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -12,6 +12,15 @@
public class ListUtils {
+ public static <T> T first(List<T> list) {
+ return list.get(0);
+ }
+
+ public static <T> T last(List<T> list) {
+ assert list instanceof ArrayList;
+ return list.get(list.size() - 1);
+ }
+
public static <T> int lastIndexMatching(List<T> list, Predicate<T> tester) {
for (int i = list.size() - 1; i >= 0; i--) {
if (tester.test(list.get(i))) {
diff --git a/src/test/examples/getmembers/B.java b/src/test/examples/getmembers/B.java
index d29d0a2..1f41948 100644
--- a/src/test/examples/getmembers/B.java
+++ b/src/test/examples/getmembers/B.java
@@ -15,9 +15,9 @@
return bazResult;
}
- synchronized static String inliner() throws Exception {
+ @NeverInline
+ static String inliner() throws Exception {
B self = new B();
return self.toBeInlined();
}
-
}
diff --git a/src/test/examples/getmembers/NeverInline.java b/src/test/examples/getmembers/NeverInline.java
new file mode 100644
index 0000000..0c5a4ef
--- /dev/null
+++ b/src/test/examples/getmembers/NeverInline.java
@@ -0,0 +1,10 @@
+// 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 getmembers;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverInline {}
diff --git a/src/test/examples/getmembers/keep-rules.txt b/src/test/examples/getmembers/keep-rules.txt
index 9319c68..9fbea4d 100644
--- a/src/test/examples/getmembers/keep-rules.txt
+++ b/src/test/examples/getmembers/keep-rules.txt
@@ -19,3 +19,7 @@
java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]);
java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);
}
+
+-neverinline class * {
+ @getmembers.NeverInline <methods>;
+}
diff --git a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index ed42134..e9e7a58 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -327,7 +327,10 @@
@SomeAnnotation(4)
static void annotatedStaticMethod() {
- synchronized (AnnotatedInterface.class) { } // Do not inline
+ // Bogus body to be included into the root set: if interface desugaring is on, root set
+ // builder will skip marking methods without code.
+ synchronized (AnnotatedInterface.class) {
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index bdc5b16..6695f46 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -209,7 +209,10 @@
@Override
public ProguardTestBuilder setMinApi(AndroidApiLevel minApiLevel) {
- throw new Unimplemented("No support for setting min api");
+ if (backend == Backend.DEX) {
+ throw new Unimplemented("No support for setting min api");
+ }
+ return self();
}
@Override
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 75fe21f..0d8e03c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -44,10 +44,8 @@
"-keepclasseswithmembers public class * {",
" public static void main(java.lang.String[]);",
"}",
- "-keepclasseswithmembers interface lambdadesugaringnplus."
- + "LambdasWithStaticAndDefaultMethods$B38302860$AnnotatedInterface{",
- " *;",
- "}",
+ "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
+ "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
"-keepattributes *Annotation*",
"-dontobfuscate",
"-allowaccessmodification");
@@ -197,6 +195,7 @@
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = false)
+ .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
.withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
@@ -206,6 +205,7 @@
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = true)
+ .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
// TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
@@ -221,6 +221,7 @@
.withMinApiLevel(AndroidApiLevel.N)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = false)
+ .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
.withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
@@ -230,6 +231,7 @@
.withMinApiLevel(AndroidApiLevel.N)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = true)
+ .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
// TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 019fed1..0305053 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -169,6 +169,8 @@
return self();
}
+ /** @deprecated use {@link #setMinApi(AndroidApiLevel)} instead. */
+ @Deprecated
public T setMinApi(TestRuntime runtime) {
if (runtime.isDex()) {
setMinApi(runtime.asDex().getMinApiLevel());
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/ConversionIntroduceInterfaceMethodTest.java
new file mode 100644
index 0000000..f38aa3b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/ConversionIntroduceInterfaceMethodTest.java
@@ -0,0 +1,153 @@
+// 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.desugar.corelib.conversionTests;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.function.Consumer;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+public class ConversionIntroduceInterfaceMethodTest extends APIConversionTestBase {
+
+ @Test
+ public void testNoInterfaceMethods() throws Exception {
+ Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(
+ MyCollectionInterface.class,
+ MyCollectionInterfaceAbstract.class,
+ MyCollection.class,
+ Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccessWithOutput(
+ StringUtils.lines(
+ "action called from j$ consumer",
+ "forEach called",
+ "action called from java consumer",
+ "forEach called"));
+ }
+
+ static class CustomLibClass {
+
+ @SuppressWarnings({"unchecked", "WeakerAccess"})
+ public static void callForeach(Iterable iterable) {
+ iterable.forEach(x -> System.out.println("action called from java consumer"));
+ }
+ }
+
+ static class Executor {
+
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ public static void main(String[] args) {
+ MyCollection<String> strings = new MyCollection<>();
+ // Call foreach with j$ consumer.
+ strings.forEach(x -> System.out.println("action called from j$ consumer"));
+ // Call foreach with java consumer.
+ CustomLibClass.callForeach(strings);
+ }
+ }
+
+ interface MyCollectionInterface<E> extends Collection<E> {
+
+ // The following method override a method from Iterable and use a desugared type.
+ // API conversion is required.
+ @Override
+ default void forEach(Consumer<? super E> action) {
+ action.accept(null);
+ System.out.println("forEach called");
+ }
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ static class MyCollection<E> implements MyCollectionInterface<E> {
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] a) {
+ return null;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> c) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+ }
+
+ interface MyCollectionInterfaceAbstract<E> extends Collection<E> {
+
+ // The following method override a method from Iterable and use a desugared type.
+ // API conversion is required.
+ @Override
+ void forEach(Consumer<? super E> action);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index e60900c..fbcc6a1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -378,6 +378,7 @@
.addKeepAttributes("LineNumberTable")
.addOptionsModification(this::configure)
.allowAccessModification()
+ .enableInliningAnnotations()
.noMinification()
.run(main)
.assertSuccessWithOutput(javaOutput);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
index 130f895..566d3f9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.classinliner.lambdas;
+import com.android.tools.r8.NeverInline;
+
public class LambdasTestClass {
private static int ID = 0;
@@ -39,14 +41,16 @@
return next();
}
- private synchronized void testStatelessLambda() {
+ @NeverInline
+ private void testStatelessLambda() {
IfaceUtil.act(() -> next());
IfaceUtil.act(LambdasTestClass::next);
IfaceUtil.act(LambdasTestClass::exact);
IfaceUtil.act(LambdasTestClass::almost);
}
- private synchronized void testStatefulLambda(String a, String b) {
+ @NeverInline
+ private void testStatefulLambda(String a, String b) {
IfaceUtil.act(() -> a);
IfaceUtil.act(() -> a + b);
IfaceUtil.act((a + b)::toLowerCase);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
index dcfe291..b3a7ce8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -5,20 +5,17 @@
package com.android.tools.r8.ir.optimize.inliner;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.util.Iterator;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -26,71 +23,89 @@
@RunWith(Parameterized.class)
public class InlineSynchronizedTest extends TestBase {
- private static final List<String> METHOD_NAMES =
- ImmutableList.of(
- "println",
- "normalInlinedSynchronized",
- "classInlinedSynchronized",
- "normalInlinedControl",
- "classInlinedControl");
-
- @Parameterized.Parameters(name = "{1}")
- public static List<Object[]> data() {
- return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- private final Backend backend;
private final TestParameters parameters;
- public InlineSynchronizedTest(TestParameters parameters, Backend backend) {
+ public InlineSynchronizedTest(TestParameters parameters) {
this.parameters = parameters;
- this.backend = backend;
}
@Test
public void test() throws Exception {
- CodeInspector codeInspector =
- testForR8(backend)
- .addProgramClasses(InlineSynchronizedTestClass.class)
- .addKeepMainRule(InlineSynchronizedTestClass.class)
- .enableInliningAnnotations()
- .noMinification()
- .compile()
- .inspector();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InlineSynchronizedTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
- ClassSubject classSubject = codeInspector.clazz(InlineSynchronizedTestClass.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.mainMethod();
- Iterator<InstructionSubject> it = methodSubject.iterateInstructions();
- int[] counts = new int[METHOD_NAMES.size()];
- while (it.hasNext()) {
- InstructionSubject instruction = it.next();
- if (!instruction.isInvoke()) {
- continue;
- }
- DexString invokedName = ((InvokeInstructionSubject) instruction).invokedMethod().name;
- int idx = METHOD_NAMES.indexOf(invokedName.toString());
- if (idx >= 0) {
- ++counts[idx];
- }
- }
- // Synchronized methods can never be inlined.
- assertCount(counts, "normalInlinedSynchronized", 1);
- assertCount(counts, "classInlinedSynchronized", 1);
- // Only the never-merge method is inlined, classInlining should run on the kept class.
- assertCount(counts, "normalInlinedControl", 0);
- assertCount(counts, "classInlinedControl", 1);
- // Double check the total.
- int total = 0;
- for (int i = 0; i < counts.length; ++i) {
- total += counts[i];
- }
- assertEquals(4, total);
+ // Check that there is a single monitor-enter instruction (for the normal inlining
+ // case).
+ assertEquals(
+ 1,
+ testClassSubject
+ .mainMethod()
+ .streamInstructions()
+ .filter(InstructionSubject::isMonitorEnter)
+ .count());
+
+ // Check that there are two monitor-exit instructions (for the normal inlining case,
+ // one for the normal exit and one for the exceptional exit).
+ assertEquals(
+ 2,
+ testClassSubject
+ .mainMethod()
+ .streamInstructions()
+ .filter(InstructionSubject::isMonitorExit)
+ .count());
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(
+ aClassSubject.uniqueMethodWithName("normalInlinedSynchronized"),
+ not(isPresent()));
+
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, not(isPresent()));
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(
+ "A::normalInlinedSynchronized", "B::classInlinedSynchronized");
}
- private static void assertCount(int counts[], String methodName, int expectedCount) {
- int idx = METHOD_NAMES.indexOf(methodName);
- assert idx >= 0;
- assertEquals(expectedCount, counts[idx]);
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // Test normal inlining.
+ new A().normalInlinedSynchronized();
+
+ // Test class-inlining.
+ new B().classInlinedSynchronized();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ synchronized void normalInlinedSynchronized() {
+ System.out.println("A::normalInlinedSynchronized");
+ }
+ }
+
+ static class B {
+
+ @NeverInline
+ synchronized void classInlinedSynchronized() {
+ System.out.println("B::classInlinedSynchronized");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
deleted file mode 100644
index 45d3a5c..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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 com.android.tools.r8.NeverInline;
-
-class InlineSynchronizedTestClass {
- private synchronized void normalInlinedSynchronized() {
- System.out.println("InlineSynchronizedTestClass::normalInlinedSynchronized");
- }
-
- public synchronized void classInlinedSynchronized() {
- System.out.println("InlineSynchronizedTestClass::classInlinedSynchronized");
- }
-
- private void normalInlinedControl() {
- System.out.println("InlineSynchronizedTestClass::normalInlinedControl");
- }
-
- @NeverInline
- public void classInlinedControl() {
- System.out.println("InlineSynchronizedTestClass::classInlinedControl");
- }
-
- public static void main(String[] args) {
- // Test normal inlining.
- InlineSynchronizedTestClass testClass = new InlineSynchronizedTestClass();
- testClass.normalInlinedSynchronized();
- testClass.normalInlinedControl();
-
- // Test class-inlining.
- new InlineSynchronizedTestClass().classInlinedSynchronized();
- new InlineSynchronizedTestClass().classInlinedControl();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
new file mode 100644
index 0000000..a8578e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
@@ -0,0 +1,133 @@
+// 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.sync;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.inliner.sync.InlineStaticSynchronizedMethodTest.TestClass.RunnableImpl;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.Thread.State;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineStaticSynchronizedMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public InlineStaticSynchronizedMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InlineStaticSynchronizedMethodTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::verifySynchronizedMethodsAreInlined)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(
+ "t1 START", "m1 ENTER", "t2 START", "m1 EXIT", "m2 ENTER", "m2 EXIT");
+ }
+
+ private void verifySynchronizedMethodsAreInlined(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(RunnableImpl.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject.uniqueMethodWithName("m1"), not(isPresent()));
+ assertThat(classSubject.uniqueMethodWithName("m2"), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ private static List<String> logs = Collections.synchronizedList(new ArrayList<>());
+
+ private static volatile Thread t1 = new Thread(new RunnableImpl(1));
+ private static volatile Thread t2 = new Thread(new RunnableImpl(2));
+
+ public static void main(String[] args) {
+ System.out.println("t1 START");
+ t1.start();
+ waitUntil(() -> logs.contains("m1 ENTER"));
+ System.out.println("t2 START");
+ t2.start();
+ }
+
+ static void log(String message) {
+ logs.add(message);
+ System.out.println(message);
+ }
+
+ static void waitUntil(BooleanSupplier condition) {
+ while (!condition.getAsBoolean()) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ static class RunnableImpl implements Runnable {
+
+ int which;
+
+ RunnableImpl(int which) {
+ this.which = which;
+ }
+
+ @Override
+ public void run() {
+ if (which == 1) {
+ m1();
+ } else {
+ m2();
+ }
+ }
+
+ static synchronized void m1() {
+ log("m1 ENTER");
+ // Intentionally not using a lambda, since we do not allow inlining of methods with an
+ // invoke-custom instruction. (This only makes a difference for the CF backend.)
+ waitUntil(
+ new BooleanSupplier() {
+
+ @Override
+ public boolean getAsBoolean() {
+ return t2.getState() == State.BLOCKED;
+ }
+ });
+ log("m1 EXIT");
+ }
+
+ static synchronized void m2() {
+ log("m2 ENTER");
+ log("m2 EXIT");
+ }
+ }
+ }
+
+ interface BooleanSupplier {
+
+ boolean getAsBoolean();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
new file mode 100644
index 0000000..18d1deb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
@@ -0,0 +1,101 @@
+// 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.sync;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlinerMonitorEnterValuesThresholdTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final int threshold;
+
+ @Parameters(name = "{0}, threshold: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), ImmutableList.of(2, 3));
+ }
+
+ public InlinerMonitorEnterValuesThresholdTest(TestParameters parameters, int threshold) {
+ this.parameters = parameters;
+ this.threshold = threshold;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.inliningMonitorEnterValuesAllowance = threshold)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject.mainMethod(), isPresent());
+ assertThat(classSubject.uniqueMethodWithName("m1"), not(isPresent()));
+ assertThat(classSubject.uniqueMethodWithName("m2"), not(isPresent()));
+ if (threshold == 2) {
+ assertThat(classSubject.uniqueMethodWithName("m3"), isPresent());
+ } else {
+ assert threshold == 3;
+ assertThat(classSubject.uniqueMethodWithName("m3"), not(isPresent()));
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ synchronized (TestClass.class) {
+ // Method m1() contributes with 1 extra monitor-enter value and should therefore always be
+ // inlined since the threshold is 2 or 3.
+ m1();
+
+ // Method m2() contributes with no extra monitor-enter values since it uses TestClass.class
+ // as a lock, which is already used as a lock in the enclosing class. Therefore, m2() should
+ // always be inlined.
+ m2();
+
+ // Method m3() contributes with 1 extra monitor-enter value and should therefore only be
+ // inlined if the threshold is 3.
+ m3();
+ }
+ }
+
+ private static void m1() {
+ synchronized (new Object()) {
+ System.out.print("Hello");
+ }
+ }
+
+ private static synchronized void m2() {
+ System.out.print(" world");
+ }
+
+ private static void m3() {
+ synchronized (new Object()) {
+ System.out.println("!");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 8f4aca9..2cd766b 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -65,8 +65,11 @@
testForR8(parameters.getBackend())
.addProgramFiles(Paths.get(appFileName))
.addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
+ .allowUnusedProguardConfigurationRules()
+ .enableProguardTestOptions()
.setMinApi(parameters.getRuntime())
- .compile().inspector();
+ .compile()
+ .inspector();
inspection.accept(parameters, codeInspector);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index 8585a9c..9e92530 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -31,10 +31,10 @@
private final TestParameters parameters;
private final boolean defaultEnumValueInAnnotation;
- @Parameterized.Parameters(name = "Backend: {0}, default value in annotation: {1}")
+ @Parameterized.Parameters(name = "{0}, default value in annotation: {1}")
public static Collection<Object[]> data() {
return buildParameters(
- getTestParameters().withAllRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
public B137392797(TestParameters parameters, boolean defaultEnumValueInAnnotation) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index ab0fc8b..d8afbb3 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -50,6 +50,10 @@
|| this == R8_CF;
}
+ public boolean isProguard() {
+ return this == PROGUARD5 || this == PROGUARD6 || this == PROGUARD6_THEN_D8;
+ }
+
public boolean isFullModeR8() {
return this == R8
|| this == R8_CF;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 2b6c5db..268a673 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -9,11 +9,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
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 com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -25,180 +29,231 @@
@RunWith(Parameterized.class)
public class IfOnAccessModifierTest extends ProguardCompatibilityTestBase {
+
private static final List<Class<?>> CLASSES =
- ImmutableList.of(ClassForIf.class, ClassForSubsequent.class, MainForAccessModifierTest.class);
+ ImmutableList.of(
+ ClassForIf.class,
+ ClassForSubsequent.class,
+ MainForAccessModifierTest.class,
+ NeverClassInline.class,
+ NeverInline.class);
+ private final TestParameters parameters;
private final Shrinker shrinker;
- private final MethodSignature nonPublicMethod;
- private final MethodSignature publicMethod;
- public IfOnAccessModifierTest(Shrinker shrinker) {
+ public IfOnAccessModifierTest(TestParameters parameters, Shrinker shrinker) {
+ this.parameters = parameters;
this.shrinker = shrinker;
- nonPublicMethod = new MethodSignature("nonPublicMethod", "void", ImmutableList.of());
- publicMethod = new MethodSignature("publicMethod", "void", ImmutableList.of());
}
- @Parameters(name = "shrinker: {0}")
- public static Collection<Object> data() {
- return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
+ @Parameters(name = "{0}, shrinker: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8));
+ }
+
+ private TestShrinkerBuilder<?, ?, ?, ?, ?> getTestBuilder() {
+ switch (shrinker) {
+ case PROGUARD6:
+ assertTrue(parameters.isCfRuntime());
+ return testForProguard();
+ case R8:
+ return testForR8(parameters.getBackend())
+ .allowUnusedProguardConfigurationRules()
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations();
+ default:
+ throw new Unreachable();
+ }
}
@Test
public void ifOnPublic_noPublicClassForIfRule() throws Exception {
- List<String> config = ImmutableList.of(
- "-repackageclasses 'top'",
- "-keep class **.Main* {",
- " public static void callIfNonPublic();",
- "}",
- "-if public class **.ClassForIf {",
- " <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, not(isPresent()));
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if public class **.ClassForIf {",
+ " <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, not(isPresent()));
+ });
}
@Test
public void ifOnNonPublic_keepOnPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfNonPublic();",
- "}",
- "-if class **.ClassForIf {",
- " !public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " !public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ });
}
@Test
public void ifOnNonPublic_keepOnNonPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfNonPublic();",
- "}",
- "-if class **.ClassForIf {",
- " !public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " !public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " !public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " !public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ });
}
@Test
public void ifOnPublic_keepOnPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfPublic();",
- "}",
- "-if class **.ClassForIf {",
- " public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ });
}
@Test
public void ifOnPublic_keepOnNonPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfPublic();",
- "}",
- "-if class **.ClassForIf {",
- " public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " !public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " !public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
index 1b5bd75..49d75f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
@@ -3,14 +3,20 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
class ClassForIf {
ClassForIf() {
}
+ @NeverInline
synchronized void nonPublicMethod() {
System.out.println("ClassForIf::nonPublicMethod");
}
+ @NeverInline
synchronized public void publicMethod() {
System.out.println("ClassForIf::publicMethod");
}
@@ -20,10 +26,12 @@
ClassForSubsequent() {
}
+ @NeverInline
synchronized void nonPublicMethod() {
System.out.println("ClassForSubsequent::nonPublicMethod");
}
+ @NeverInline
synchronized public void publicMethod() {
System.out.println("ClassForSubsequent::publicMethod");
}