Merge commit 'edb4e36cad16eea3915ae78f017f934c14604b44' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index d667575..a12020f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -483,7 +483,6 @@
assert !internal.inlinerOptions().enableInlining;
assert !internal.enableClassInlining;
assert !internal.enableVerticalClassMerging;
- assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
assert !internal.outline.enabled;
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index c39a041..41f4030 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -179,7 +179,6 @@
assert !internal.inlinerOptions().enableInlining;
assert !internal.enableClassInlining;
assert !internal.enableVerticalClassMerging;
- assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
assert !internal.outline.enabled;
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index b27f94a..3e235b4 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -530,6 +530,10 @@
return false;
}
+ public boolean isArgumentPropagatorGraphLens() {
+ return false;
+ }
+
public boolean isClearCodeRewritingLens() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
index b2aded5..976020c6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
@@ -74,6 +74,7 @@
}
methods.add(singleTarget.getReference());
+ continue;
}
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 7f36c0c..f13bd76 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -333,7 +333,7 @@
}
if (metadata != null) {
- if (isUnusedReadAfterMethodStaticizing(metadata)) {
+ if (isUnusedReadAfterMethodStaticizing(field, metadata)) {
// Ignore this read.
dependencies
.computeIfAbsent(field.getDefinition(), ignoreKey(ProgramMethodSet::createConcurrent))
@@ -367,8 +367,10 @@
}
}
- private boolean isUnusedReadAfterMethodStaticizing(BytecodeInstructionMetadata metadata) {
- if (!metadata.isReadForInvokeReceiver()) {
+ private boolean isUnusedReadAfterMethodStaticizing(
+ DexClassAndField field, BytecodeInstructionMetadata metadata) {
+ if (!metadata.isReadForInvokeReceiver()
+ || field.getOptimizationInfo().getDynamicType().getNullability().isMaybeNull()) {
return false;
}
Set<DexMethod> readForInvokeReceiver = metadata.getReadForInvokeReceiver();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
index edcfbc6..2d090e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
@@ -72,7 +72,10 @@
@Override
public boolean equals(Object o) {
- if (getClass() != o.getClass()) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
return false;
}
StatefulObjectValue statefulObjectValue = (StatefulObjectValue) o;
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 e44b158..6a237d0 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
@@ -44,11 +44,13 @@
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.function.Consumer;
@@ -1085,20 +1087,41 @@
+ BooleanUtils.intValue(!context().getDefinition().isStatic());
}
+ public Iterator<Argument> argumentIterator() {
+ return new Iterator<Argument>() {
+
+ private final InstructionIterator instructionIterator = entryBlock().iterator();
+ private Argument next = instructionIterator.next().asArgument();
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public Argument next() {
+ if (next == null) {
+ throw new NoSuchElementException();
+ }
+ Argument previous = next;
+ next = instructionIterator.next().asArgument();
+ return previous;
+ }
+ };
+ }
+
public List<Value> collectArguments() {
return collectArguments(false);
}
public List<Value> collectArguments(boolean ignoreReceiver) {
final List<Value> arguments = new ArrayList<>();
- InstructionIterator iterator = entryBlock().iterator();
+ Iterator<Argument> iterator = argumentIterator();
while (iterator.hasNext()) {
- Instruction instruction = iterator.next();
- if (instruction.isArgument()) {
- Value out = instruction.asArgument().outValue();
- if (!ignoreReceiver || !out.isThis()) {
- arguments.add(out);
- }
+ Argument argument = iterator.next();
+ Value out = argument.outValue();
+ if (!ignoreReceiver || !out.isThis()) {
+ arguments.add(out);
}
}
assert arguments.size()
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 9fd0c74..5de6390 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -180,6 +180,16 @@
.build();
}
+ public Position replaceOutermostCallerPosition(Position newOutermostCallerPosition) {
+ if (!hasCallerPosition()) {
+ return newOutermostCallerPosition;
+ }
+ return builderWithCopy()
+ .setCallerPosition(
+ getCallerPosition().replaceOutermostCallerPosition(newOutermostCallerPosition))
+ .build();
+ }
+
@Override
public final boolean equals(Object other) {
return Equatable.equalsImpl(this, other);
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 45758d3..c19a35b 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
@@ -89,7 +89,6 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.outliner.Outliner;
-import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -104,7 +103,6 @@
import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.CfgPrinter;
-import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -141,7 +139,6 @@
private final StringBuilderOptimizer stringBuilderOptimizer;
private final IdempotentFunctionCallCanonicalizer idempotentFunctionCallCanonicalizer;
private final ClassInliner classInliner;
- private final ClassStaticizer classStaticizer;
private final InternalOptions options;
private final CfgPrinter printer;
public final CodeRewriter codeRewriter;
@@ -228,7 +225,6 @@
this.covariantReturnTypeAnnotationTransformer = null;
this.dynamicTypeOptimization = null;
this.classInliner = null;
- this.classStaticizer = null;
this.fieldAccessAnalysis = null;
this.libraryMethodOverrideAnalysis = null;
this.inliner = null;
@@ -263,8 +259,6 @@
options.enableClassInlining && options.inlinerOptions().enableInlining
? new ClassInliner()
: null;
- this.classStaticizer =
- options.enableClassStaticizer ? new ClassStaticizer(appViewWithLiveness, this) : null;
this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
this.fieldAccessAnalysis = new FieldAccessAnalysis(appViewWithLiveness);
this.libraryMethodOverrideAnalysis =
@@ -295,7 +289,6 @@
} else {
this.assumeInserter = null;
this.classInliner = null;
- this.classStaticizer = null;
this.dynamicTypeOptimization = null;
this.fieldAccessAnalysis = null;
this.libraryMethodOverrideAnalysis = null;
@@ -350,13 +343,6 @@
D8NestBasedAccessDesugaring::clearNestAttributes);
}
- private void staticizeClasses(OptimizationFeedback feedback, ExecutorService executorService)
- throws ExecutionException {
- if (classStaticizer != null) {
- classStaticizer.staticizeCandidates(feedback, executorService);
- }
- }
-
private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
if (covariantReturnTypeAnnotationTransformer != null) {
covariantReturnTypeAnnotationTransformer.process(builder);
@@ -652,10 +638,6 @@
appView.withArgumentPropagator(
argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing));
enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
- ConsumerUtils.acceptIfNotNull(
- classStaticizer,
- classStaticizer ->
- classStaticizer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass));
outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
if (fieldAccessAnalysis != null) {
@@ -727,11 +709,6 @@
GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
- ConsumerUtils.acceptIfNotNull(
- classStaticizer,
- classStaticizer ->
- classStaticizer.prepareForSecondaryOptimizationPass(
- graphLensForSecondaryOptimizationPass));
outliner.rewriteWithLens();
timing.begin("IR conversion phase 2");
@@ -765,17 +742,6 @@
// staticizer and phase 3 does not perform again the rewriting.
appView.clearCodeRewritings();
- // TODO(b/112831361): Implement support for staticizeClasses in CF backend.
- if (!options.isGeneratingClassFiles()) {
- printPhase("Class staticizer post processing");
- // TODO(b/127694949): Adapt to PostOptimization.
- staticizeClasses(feedback, executorService);
- feedback.updateVisibleOptimizationInfo();
- // The class staticizer lens shall not be applied through lens code rewriting or it breaks
- // the lambda merger.
- appView.clearCodeRewritings();
- }
-
// Commit synthetics before creating a builder (otherwise the builder will not include the
// synthetics.)
commitPendingSyntheticItemsR8(appView);
@@ -1474,12 +1440,6 @@
previous = printMethod(code, "IR after argument type logging (SSA)", previous);
- if (classStaticizer != null) {
- timing.begin("Identify staticizing candidates");
- classStaticizer.examineMethodCode(code);
- timing.end();
- }
-
assert code.verifyTypes(appView);
deadCodeRemover.run(code, timing);
@@ -1972,9 +1932,6 @@
appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.onMethodPruned(method));
enumUnboxer.onMethodPruned(method);
outliner.onMethodPruned(method);
- if (classStaticizer != null) {
- classStaticizer.onMethodPruned(method);
- }
if (inliner != null) {
inliner.onMethodPruned(method);
}
@@ -1992,9 +1949,6 @@
argumentPropagator -> argumentPropagator.onMethodCodePruned(method));
enumUnboxer.onMethodCodePruned(method);
outliner.onMethodCodePruned(method);
- if (classStaticizer != null) {
- classStaticizer.onMethodCodePruned(method);
- }
if (inliner != null) {
inliner.onMethodCodePruned(method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index a52190d..8bcdea8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -103,6 +103,7 @@
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.android.tools.r8.optimize.argumentpropagation.lenscoderewriter.NullCheckInserter;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
@@ -235,6 +236,8 @@
new LazyBox<>(() -> new LensCodeRewriterUtils(appView, graphLens, codeLens));
InterfaceTypeToClassTypeLensCodeRewriterHelper interfaceTypeToClassTypeRewriterHelper =
InterfaceTypeToClassTypeLensCodeRewriterHelper.create(appView, code, graphLens, codeLens);
+ NullCheckInserter nullCheckInserter =
+ NullCheckInserter.create(appView, code, graphLens, codeLens);
boolean mayHaveUnreachableBlocks = false;
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
@@ -336,9 +339,6 @@
Invoke.Type actualInvokeType = lensLookup.getType();
iterator =
- insertNullCheckForInvokeReceiverIfNeeded(
- code, blocks, iterator, invoke, lensLookup);
- iterator =
insertCastsForInvokeArgumentsIfNeeded(code, blocks, iterator, invoke, lensLookup);
RewrittenPrototypeDescription prototypeChanges = lensLookup.getPrototypeChanges();
@@ -498,6 +498,9 @@
interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
invoke, newInvoke, lensLookup, blocks, block, iterator);
+ nullCheckInserter.insertNullCheckForInvokeReceiverIfNeeded(
+ invoke, newInvoke, lensLookup);
+
if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
@@ -785,10 +788,11 @@
new DestructivePhiTypeUpdater(appView, graphLens, codeLens)
.recomputeAndPropagateTypes(code, affectedPhis);
}
+ nullCheckInserter.processWorklist();
code.removeAllDeadAndTrivialPhis();
removeUnusedArguments(method, code, unusedArguments);
- // Finalize cast insertion.
+ // Finalize cast and null check insertion.
interfaceTypeToClassTypeRewriterHelper.processWorklist();
assert code.isConsistentSSABeforeTypesAreCorrect();
@@ -984,56 +988,6 @@
return iterator;
}
- private InstructionListIterator insertNullCheckForInvokeReceiverIfNeeded(
- IRCode code,
- BasicBlockIterator blocks,
- InstructionListIterator iterator,
- InvokeMethod invoke,
- MethodLookupResult lookup) {
- // If the invoke has been staticized, then synthesize a null check for the receiver.
- if (!invoke.isInvokeMethodWithReceiver()) {
- return iterator;
- }
-
- ArgumentInfo receiverArgumentInfo =
- lookup.getPrototypeChanges().getArgumentInfoCollection().getArgumentInfo(0);
- if (!receiverArgumentInfo.isRemovedArgumentInfo()
- || !receiverArgumentInfo.asRemovedArgumentInfo().isCheckNullOrZeroSet()) {
- return iterator;
- }
-
- assert lookup.getType().isStatic();
-
- TypeElement receiverType = invoke.asInvokeMethodWithReceiver().getReceiver().getType();
- if (receiverType.isDefinitelyNotNull()) {
- return iterator;
- }
-
- iterator.previous();
-
- Position nullCheckPosition =
- invoke
- .getPosition()
- .getOutermostCallerMatchingOrElse(
- Position::isRemoveInnerFramesIfThrowingNpe, invoke.getPosition());
- InvokeMethod nullCheck =
- iterator.insertNullCheckInstruction(
- appView, code, blocks, invoke.getFirstArgument(), nullCheckPosition);
-
- // Reset the block iterator.
- if (invoke.getBlock().hasCatchHandlers()) {
- BasicBlock splitBlock = invoke.getBlock();
- BasicBlock previousBlock = blocks.previousUntil(block -> block == splitBlock);
- assert previousBlock == splitBlock;
- blocks.next();
- iterator = splitBlock.listIterator(code);
- }
-
- Instruction next = iterator.next();
- assert next == invoke;
- return iterator;
- }
-
private InstructionListIterator insertCastsForInvokeArgumentsIfNeeded(
IRCode code,
BasicBlockIterator blocks,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index 0702fc4..4adcf5f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -236,8 +236,4 @@
DexMethodHandle newHandle = rewriteDexMethodHandle(oldHandle, use, context);
return newHandle != oldHandle ? new DexValueMethodHandle(newHandle) : methodHandle;
}
-
- public boolean hasGraphLens(GraphLens graphLens, GraphLens codeLens) {
- return this.graphLens == graphLens && this.codeLens == codeLens;
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 8b229f9..0da54ff 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -54,8 +54,6 @@
void markAsPropagated(DexEncodedMethod method);
- void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
-
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo);
@@ -87,8 +85,6 @@
void unsetBridgeInfo(DexEncodedMethod method);
- void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method);
-
void unsetClassInitializerMayBePostponed(ProgramMethod method);
void unsetClassInlinerMethodConstraint(ProgramMethod method);
@@ -131,7 +127,6 @@
if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
unsetAbstractReturnValue(method);
unsetBridgeInfo(method.getDefinition());
- unsetCheckNullReceiverBeforeAnySideEffect(method);
unsetClassInitializerMayBePostponed(method);
unsetClassInlinerMethodConstraint(method);
unsetDynamicReturnType(method);
@@ -158,8 +153,5 @@
methodNeverReturnsNormally(method);
setUnusedArguments(
method, BitSetUtils.createFilled(true, method.getDefinition().getNumberOfArguments()));
- if (method.getDefinition().isInstance()) {
- markCheckNullReceiverBeforeAnySideEffect(method.getDefinition(), true);
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 69cc40b..33c69ac 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -91,8 +91,10 @@
if (appView.options().enableBackportedMethodRewriting()) {
backportedMethodRewriter = new BackportedMethodRewriter(appView);
}
- if (appView.options().apiModelingOptions().enableOutliningOfMethods) {
- yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
+ if (appView.options().apiModelingOptions().enableOutliningOfMethods
+ && appView.hasClassHierarchy()) {
+ yieldingDesugarings.add(
+ new ApiInvokeOutlinerDesugaring(appView.withClassHierarchy(), apiLevelCompute));
}
if (appView.options().enableTryWithResourcesDesugaring()) {
desugarings.add(new TwrInstructionDesugaring(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 8e2f9fb..a41ea13 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -20,6 +21,8 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
@@ -38,10 +41,13 @@
*/
public class ApiInvokeOutlinerDesugaring implements CfInstructionDesugaring {
- private final AppView<?> appView;
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final AndroidApiLevelCompute apiLevelCompute;
- public ApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+ public ApiInvokeOutlinerDesugaring(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ AndroidApiLevelCompute apiLevelCompute) {
+ // TODO(b/216277460) We should only need AppView<?>.
this.appView = appView;
this.apiLevelCompute = apiLevelCompute;
}
@@ -89,8 +95,8 @@
if (!holderType.isClassType()) {
return appView.computedMinApiLevel();
}
- DexClass clazz = appView.definitionFor(holderType);
- if (clazz == null || !clazz.isLibraryClass()) {
+ DexClass holder = appView.definitionFor(holderType);
+ if (holder == null || !holder.isLibraryClass()) {
return appView.computedMinApiLevel();
}
ComputedApiLevel methodApiLevel =
@@ -103,9 +109,21 @@
// Compute the api level of the holder to see if the method will be stubbed.
ComputedApiLevel holderApiLevel =
apiLevelCompute.computeApiLevelForLibraryReference(holderType, ComputedApiLevel.unknown());
- return methodApiLevel.isGreaterThan(holderApiLevel)
- ? methodApiLevel
- : appView.computedMinApiLevel();
+ if (holderApiLevel.isGreaterThanOrEqualTo(methodApiLevel)) {
+ return appView.computedMinApiLevel();
+ }
+ // Check for protected or package private access flags before outlining.
+ if (holder.isInterface()) {
+ return methodApiLevel;
+ } else {
+ MethodResolutionResult methodResolutionResult =
+ appView.appInfo().resolveMethod(cfInvoke.getMethod(), false);
+ SingleResolutionResult singleResolutionResult = methodResolutionResult.asSingleResolution();
+ return singleResolutionResult != null
+ && !singleResolutionResult.getResolvedMethod().isPublic()
+ ? methodApiLevel
+ : appView.computedMinApiLevel();
+ }
}
private boolean isApiLevelLessThanOrEqualTo9(ComputedApiLevel apiLevel) {
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 4e6bea5..a39688a 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
@@ -374,15 +374,9 @@
// receiver. Therefore, if the receiver may be null and the candidate inlinee does not
// throw if the receiver is null before any other side effect, then we must synthesize a
// null check.
- if (!singleTarget
- .getDefinition()
- .getOptimizationInfo()
- .checksNullReceiverBeforeAnySideEffect()) {
- if (!inlinerOptions.enableInliningOfInvokesWithNullableReceivers) {
- whyAreYouNotInliningReporter.reportReceiverMaybeNull();
- return null;
- }
- action.setShouldSynthesizeNullCheckForReceiver();
+ if (!inlinerOptions.enableInliningOfInvokesWithNullableReceivers) {
+ whyAreYouNotInliningReporter.reportReceiverMaybeNull();
+ return null;
}
}
return action;
@@ -484,9 +478,7 @@
ProgramMethod singleTarget,
InliningIRProvider inliningIRProvider,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- boolean removeInnerFramesIfThrowingNpe = false;
- IRCode inlinee =
- inliningIRProvider.getInliningIR(invoke, singleTarget, removeInnerFramesIfThrowingNpe);
+ IRCode inlinee = inliningIRProvider.getInliningIR(invoke, singleTarget);
// In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
// Newly Created Objects" it says:
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 785c461..834e78d 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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.canInlineWithoutSynthesizingNullCheckForReceiver;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static com.google.common.base.Predicates.not;
@@ -51,6 +52,7 @@
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.SimpleEffectAnalysisResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -515,7 +517,6 @@
final Reason reason;
private boolean shouldSynthesizeInitClass;
- private boolean shouldSynthesizeNullCheckForReceiver;
private DexProgramClass downcastClass;
@@ -539,15 +540,9 @@
}
void setShouldSynthesizeInitClass() {
- assert !shouldSynthesizeNullCheckForReceiver;
shouldSynthesizeInitClass = true;
}
- void setShouldSynthesizeNullCheckForReceiver() {
- assert !shouldSynthesizeInitClass;
- shouldSynthesizeNullCheckForReceiver = true;
- }
-
InlineeWithReason buildInliningIR(
AppView<? extends AppInfoWithClassHierarchy> appView,
InvokeMethod invoke,
@@ -558,12 +553,7 @@
InternalOptions options = appView.options();
// Build the IR for a yet not processed method, and perform minimal IR processing.
- boolean removeInnerFramesIfThrowingNpe =
- invoke.isInvokeMethodWithReceiver()
- && invoke.asInvokeMethodWithReceiver().getReceiver().isMaybeNull()
- && !shouldSynthesizeNullCheckForReceiver;
- IRCode code =
- inliningIRProvider.getInliningIR(invoke, target, removeInnerFramesIfThrowingNpe);
+ IRCode code = inliningIRProvider.getInliningIR(invoke, target);
// Insert a init class instruction if this is needed to preserve class initialization
// semantics.
@@ -582,11 +572,24 @@
target.getDefinition().isSynchronized() && options.isGeneratingClassFiles();
boolean isSynthesizingNullCheckForReceiverUsingMonitorEnter =
shouldSynthesizeMonitorEnterExit && !target.getDefinition().isStatic();
- if (shouldSynthesizeNullCheckForReceiver
+ if (invoke.isInvokeMethodWithReceiver()
+ && invoke.asInvokeMethodWithReceiver().getReceiver().isMaybeNull()
&& !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
- synthesizeNullCheckForReceiver(appView, code, invoke);
+ SimpleEffectAnalysisResult checksReceiverBeingNull =
+ canInlineWithoutSynthesizingNullCheckForReceiver(appView, code);
+ if (!checksReceiverBeingNull.hasResult()
+ || !checksReceiverBeingNull.isPartial()
+ || (checksReceiverBeingNull.isPartial()
+ && checksReceiverBeingNull.topMostNotSatisfiedBlockSize() > 1)) {
+ synthesizeNullCheckForReceiver(appView, code, invoke, code.entryBlock());
+ } else {
+ checksReceiverBeingNull.forEachSatisfyingInstruction(
+ this::setRemoveInnerFramePositionForReceiverUse);
+ // Also add a null check on failing paths
+ checksReceiverBeingNull.forEachTopMostNotSatisfiedBlock(
+ block -> synthesizeNullCheckForReceiver(appView, code, invoke, block));
+ }
}
-
// Insert monitor-enter and monitor-exit instructions if the method is synchronized.
if (shouldSynthesizeMonitorEnterExit) {
TypeElement throwableType =
@@ -727,18 +730,15 @@
}
private void synthesizeNullCheckForReceiver(
- AppView<?> appView, IRCode code, InvokeMethod invoke) {
+ AppView<?> appView, IRCode code, InvokeMethod invoke, BasicBlock block) {
List<Value> arguments = code.collectArguments();
if (!arguments.isEmpty()) {
Value receiver = arguments.get(0);
assert receiver.isThis();
-
- BasicBlock entryBlock = code.entryBlock();
-
// Insert a new block between the last argument instruction and the first actual
// instruction of the method.
BasicBlock throwBlock =
- entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
+ block.listIterator(code, block.isEntry() ? arguments.size() : 0).split(code, 0, null);
assert !throwBlock.hasCatchHandlers();
InstructionListIterator iterator = throwBlock.listIterator(code);
@@ -754,6 +754,21 @@
assert false : "Unable to synthesize a null check for the receiver";
}
}
+
+ private void setRemoveInnerFramePositionForReceiverUse(Instruction instruction) {
+ Position position = instruction.getPosition();
+ if (position == null) {
+ assert false : "Expected position for inlinee call to receiver";
+ return;
+ }
+ Position removeInnerFrame =
+ position
+ .getOutermostCaller()
+ .builderWithCopy()
+ .setRemoveInnerFramesIfThrowingNpe(true)
+ .build();
+ instruction.forceOverwritePosition(position.replaceOutermostCallerPosition(removeInnerFrame));
+ }
}
public static class RetryAction extends InlineResult {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
new file mode 100644
index 0000000..8942395
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
@@ -0,0 +1,324 @@
+// Copyright (c) 2022, 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;
+
+import static com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.InstructionEffect.NO_EFFECT;
+import static com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.InstructionEffect.OTHER_EFFECT;
+import static com.android.tools.r8.utils.TraversalContinuation.BREAK;
+import static com.android.tools.r8.utils.TraversalContinuation.CONTINUE;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.DFSNodeWithState;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.StatefulDepthFirstSearchWorkList;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class SimpleDominatingEffectAnalysis {
+
+ public enum InstructionEffect {
+ NO_EFFECT,
+ DESIRED_EFFECT,
+ OTHER_EFFECT;
+
+ public boolean isDesired() {
+ return this == DESIRED_EFFECT;
+ }
+
+ public boolean isOther() {
+ return this == OTHER_EFFECT;
+ }
+
+ public boolean isNoEffect() {
+ return this == NO_EFFECT;
+ }
+
+ public static InstructionEffect fromBoolean(boolean value) {
+ return value ? DESIRED_EFFECT : OTHER_EFFECT;
+ }
+
+ public ResultState toResultState() {
+ switch (this) {
+ case NO_EFFECT:
+ return ResultState.NOT_COMPUTED;
+ case DESIRED_EFFECT:
+ return ResultState.SATISFIED;
+ default:
+ assert isOther();
+ return ResultState.NOT_SATISFIED;
+ }
+ }
+ }
+
+ /**
+ * ResultState encodes a lattice on the following form: PARTIAL / \ SATISFIED NOT_SATISFIED \ /
+ * NOT_COMPUTED
+ *
+ * <p>PARTIAL results occur when have control flow where one or more branches are SATISFIED and
+ * one or more branches are NOT_SATISFIED.
+ */
+ private enum ResultState {
+ PARTIAL,
+ SATISFIED,
+ NOT_SATISFIED,
+ NOT_COMPUTED;
+
+ public boolean isPartial() {
+ return this == PARTIAL;
+ }
+
+ public boolean isSatisfied() {
+ return this == SATISFIED;
+ }
+
+ public boolean isNotSatisfied() {
+ return this == NOT_SATISFIED;
+ }
+
+ public boolean isNotComputed() {
+ return this == NOT_COMPUTED;
+ }
+
+ public ResultState join(ResultState other) {
+ if (isPartial() || other.isNotComputed()) {
+ return this;
+ }
+ if (isNotComputed() || other.isPartial()) {
+ return other;
+ }
+ if (this == other) {
+ return this;
+ }
+ return PARTIAL;
+ }
+ }
+
+ private static class ResultStateWithPartialBlocks {
+
+ private final ResultState state;
+ private final List<BasicBlock> failingBlocks;
+
+ private ResultStateWithPartialBlocks(ResultState state, List<BasicBlock> failingBlocks) {
+ this.state = state;
+ this.failingBlocks = failingBlocks;
+ }
+
+ public ResultStateWithPartialBlocks joinChildren(
+ List<DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>> childNodes) {
+ assert state.isNotComputed();
+ ResultState newState =
+ childNodes.isEmpty() ? ResultState.NOT_SATISFIED : ResultState.NOT_COMPUTED;
+ for (DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode : childNodes) {
+ ResultStateWithPartialBlocks childState = childNode.getState();
+ assert !childState.state.isNotComputed();
+ newState = newState.join(childState.state);
+ }
+ assert !newState.isNotComputed();
+ List<BasicBlock> newFailingBlocks = new ArrayList<>();
+ if (newState.isPartial()) {
+ // Compute the initial basic blocks where that leads to OTHER_EFFECT.
+ for (DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode : childNodes) {
+ if (childNode.getState().state.isNotSatisfied()) {
+ newFailingBlocks.add(childNode.getNode());
+ } else if (childNode.getState().state.isPartial()) {
+ newFailingBlocks.addAll(childNode.getState().failingBlocks);
+ }
+ }
+ }
+ return new ResultStateWithPartialBlocks(newState, newFailingBlocks);
+ }
+ }
+
+ // The instruction analysis drives the SimpleDominatingEffectAnalysis by assigning an effect
+ // to a basic block.
+ public interface InstructionAnalysis {
+
+ // Analyse the instruction and assign and effect. If the desired effect is seen, it should
+ // dominate other users and DESIRED_EFFECT should be returned. If a violating effect is seen
+ // return OTHER_EFFECT. When an effect is seen there is no need to visit successor instructions
+ // in the block or successor blocks.
+ // If the instruction do not violate the effect, use NO_EFFECT. The analysis will, if the entire
+ // block is visited without any effect, visit successor blocks until an effect is found, the
+ // depth is violated or we see a return for which the result of this path will be notSatisfied.
+ InstructionEffect analyze(Instruction instruction);
+
+ // Return the successors of the block. The default is to only look at normal successors.
+ default List<BasicBlock> getSuccessors(BasicBlock block) {
+ return block.getNormalSuccessors();
+ }
+
+ // The max bound on instructions to consider before giving up.
+ default int maxNumberOfInstructions() {
+ return 100;
+ }
+ }
+
+ public static class SimpleEffectAnalysisResult {
+
+ private final List<Instruction> satisfyingInstructions;
+ private final List<BasicBlock> topmostNotSatisfiedBlocks;
+
+ private SimpleEffectAnalysisResult(
+ List<Instruction> satisfyingInstructions, List<BasicBlock> topmostNotSatisfiedBlocks) {
+
+ this.satisfyingInstructions = satisfyingInstructions;
+ this.topmostNotSatisfiedBlocks = topmostNotSatisfiedBlocks;
+ }
+
+ public boolean hasResult() {
+ return true;
+ }
+
+ public void forEachSatisfyingInstruction(Consumer<Instruction> instructionConsumer) {
+ satisfyingInstructions.forEach(instructionConsumer);
+ }
+
+ public void forEachTopMostNotSatisfiedBlock(Consumer<BasicBlock> blockConsumer) {
+ topmostNotSatisfiedBlocks.forEach(blockConsumer);
+ }
+
+ public int topMostNotSatisfiedBlockSize() {
+ return topmostNotSatisfiedBlocks.size();
+ }
+
+ public static SimpleEffectAnalysisResultBuilder builder() {
+ return new SimpleEffectAnalysisResultBuilder();
+ }
+
+ public boolean isPartial() {
+ return !satisfyingInstructions.isEmpty() && !topmostNotSatisfiedBlocks.isEmpty();
+ }
+ }
+
+ private static class SimpleEffectAnalysisResultBuilder {
+
+ List<Instruction> satisfyingInstructions = new ArrayList<>();
+ List<BasicBlock> failingBlocksForPartialResults = ImmutableList.of();
+ private boolean isFailed;
+
+ public void fail() {
+ isFailed = true;
+ }
+
+ public void addSatisfyingInstruction(Instruction instruction) {
+ satisfyingInstructions.add(instruction);
+ }
+
+ public void setFailingBlocksForPartialResults(List<BasicBlock> basicBlocks) {
+ this.failingBlocksForPartialResults = basicBlocks;
+ }
+
+ public SimpleEffectAnalysisResult build() {
+ return isFailed
+ ? NO_RESULT
+ : new SimpleEffectAnalysisResult(satisfyingInstructions, failingBlocksForPartialResults);
+ }
+ }
+
+ private static final SimpleEffectAnalysisResult NO_RESULT =
+ new SimpleEffectAnalysisResult(ImmutableList.of(), ImmutableList.of()) {
+ @Override
+ public boolean hasResult() {
+ return false;
+ }
+ };
+
+ public static SimpleEffectAnalysisResult run(IRCode code, InstructionAnalysis analysis) {
+ SimpleEffectAnalysisResultBuilder builder = SimpleEffectAnalysisResult.builder();
+ IntBox visitedInstructions = new IntBox();
+ new StatefulDepthFirstSearchWorkList<BasicBlock, ResultStateWithPartialBlocks>() {
+
+ @Override
+ protected TraversalContinuation process(
+ DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
+ Function<BasicBlock, DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>>
+ childNodeConsumer) {
+ InstructionEffect effect = NO_EFFECT;
+ for (Instruction instruction : node.getNode().getInstructions()) {
+ if (visitedInstructions.getAndIncrement() > analysis.maxNumberOfInstructions()) {
+ builder.fail();
+ return BREAK;
+ }
+ effect = analysis.analyze(instruction);
+ if (!effect.isNoEffect()) {
+ if (effect.isDesired()) {
+ builder.addSatisfyingInstruction(instruction);
+ }
+ break;
+ }
+ }
+ if (effect.isNoEffect()) {
+ List<BasicBlock> successors = analysis.getSuccessors(node.getNode());
+ for (BasicBlock successor : successors) {
+ DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode =
+ childNodeConsumer.apply(successor);
+ if (childNode.hasState()) {
+ // If we see a block where the children have not been processed we cannot guarantee
+ // all paths having the effect since - ex. we could have a non-terminating loop.
+ builder.fail();
+ return BREAK;
+ }
+ }
+ }
+ node.setState(new ResultStateWithPartialBlocks(effect.toResultState(), ImmutableList.of()));
+ return CONTINUE;
+ }
+
+ @Override
+ protected TraversalContinuation joiner(
+ DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
+ List<DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>> childNodes) {
+ ResultStateWithPartialBlocks resultState = node.getState();
+ if (resultState.state.isNotComputed()) {
+ resultState = resultState.joinChildren(childNodes);
+ } else {
+ assert resultState.state.isSatisfied() || resultState.state.isNotSatisfied();
+ assert childNodes.isEmpty();
+ }
+ node.setState(resultState);
+ if (node.getNode().isEntry()) {
+ builder.setFailingBlocksForPartialResults(resultState.failingBlocks);
+ }
+ return CONTINUE;
+ }
+ }.run(code.entryBlock());
+
+ return builder.build();
+ }
+
+ public static SimpleEffectAnalysisResult canInlineWithoutSynthesizingNullCheckForReceiver(
+ AppView<?> appView, IRCode code) {
+ assert code.context().getDefinition().isVirtualMethod();
+ Value receiver = code.getThis();
+ if (!receiver.isUsed()) {
+ return NO_RESULT;
+ }
+ ProgramMethod context = code.context();
+ return run(
+ code,
+ instruction -> {
+ if ((instruction.isInvokeMethodWithReceiver()
+ && instruction.asInvokeMethodWithReceiver().getReceiver() == receiver)
+ || (instruction.isInstanceFieldInstruction()
+ && instruction.asInstanceFieldInstruction().object() == receiver)
+ || (instruction.isMonitorEnter() && instruction.asMonitor().object() == receiver)) {
+ // Conservatively bailout if there are catch handlers.
+ return InstructionEffect.fromBoolean(!instruction.getBlock().hasCatchHandlers());
+ }
+ return instruction.instructionMayHaveSideEffects(appView, context)
+ ? OTHER_EFFECT
+ : NO_EFFECT;
+ });
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 06e2b18..93b5d8f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -75,7 +75,7 @@
// Not a direct inlinee.
continue;
}
- IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee, false);
+ IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
int increment =
inlinee.getDefinition().getCode().estimatedSizeForInlining()
- estimateSizeOfNonMaterializingInstructions(invoke, inliningIR);
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 09b1cc3..95483ce 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
@@ -31,7 +31,6 @@
static int UNKNOWN_RETURNED_ARGUMENT = -1;
static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
static AbstractValue UNKNOWN_ABSTRACT_RETURN_VALUE = UnknownValue.getInstance();
- static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
@@ -167,11 +166,6 @@
}
@Override
- public boolean checksNullReceiverBeforeAnySideEffect() {
- return UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
- }
-
- @Override
public boolean triggersClassInitBeforeAnySideEffect() {
return UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index a4667c9..17cffe5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -88,8 +88,6 @@
public abstract boolean forceInline();
- public abstract boolean checksNullReceiverBeforeAnySideEffect();
-
public abstract boolean triggersClassInitBeforeAnySideEffect();
public abstract boolean mayHaveSideEffects();
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
index a0c4eb2..acf0da6 100644
--- 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
@@ -542,11 +542,6 @@
// Identifies if the method preserves class initialization after inlining.
feedback.markTriggerClassInitBeforeAnySideEffect(
method, triggersClassInitializationBeforeSideEffect(code));
- } else {
- // Identifies if the method preserves null check of the receiver after inlining.
- Value receiver = code.getThis();
- feedback.markCheckNullReceiverBeforeAnySideEffect(
- method, receiver.isUsed() && checksNullBeforeSideEffect(code, receiver));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 64bdb91..69f0497 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -89,7 +89,7 @@
private static final int UNUSED_FLAG_1 = 0x20;
private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
private static final int UNUSED_FLAG_2 = 0x80;
- private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100;
+ private static final int UNUSED_FLAG_3 = 0x100;
private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200;
private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
private static final int REACHABILITY_SENSITIVE_FLAG = 0x800;
@@ -116,9 +116,7 @@
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
defaultFlags |= 0 * UNUSED_FLAG_2;
- defaultFlags |=
- BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect())
- * CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG;
+ defaultFlags |= 0 * UNUSED_FLAG_3;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.triggersClassInitBeforeAnySideEffect())
* TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG;
@@ -488,11 +486,6 @@
}
@Override
- public boolean checksNullReceiverBeforeAnySideEffect() {
- return isFlagSet(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
- }
-
- @Override
public boolean triggersClassInitBeforeAnySideEffect() {
return isFlagSet(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG);
}
@@ -671,14 +664,6 @@
}
}
- void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
- setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
- }
-
- void unsetCheckNullReceiverBeforeAnySideEffect() {
- clearFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
- }
-
void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
setFlag(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 5e9386c..4b6241a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -229,12 +229,6 @@
}
@Override
- public synchronized void markCheckNullReceiverBeforeAnySideEffect(
- DexEncodedMethod method, boolean mark) {
- getMethodOptimizationInfoForUpdating(method).markCheckNullReceiverBeforeAnySideEffect(mark);
- }
-
- @Override
public synchronized void markTriggerClassInitBeforeAnySideEffect(
DexEncodedMethod method, boolean mark) {
getMethodOptimizationInfoForUpdating(method).markTriggerClassInitBeforeAnySideEffect(mark);
@@ -311,11 +305,6 @@
}
@Override
- public synchronized void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
- getMethodOptimizationInfoForUpdating(method).unsetCheckNullReceiverBeforeAnySideEffect();
- }
-
- @Override
public synchronized void unsetClassInitializerMayBePostponed(ProgramMethod method) {
getMethodOptimizationInfoForUpdating(method).unsetClassInitializerMayBePostponed();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index fd6d27e..ac66916 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -94,9 +94,6 @@
public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {}
@Override
- public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
-
- @Override
public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
@Override
@@ -143,9 +140,6 @@
public void unsetBridgeInfo(DexEncodedMethod method) {}
@Override
- public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {}
-
- @Override
public void unsetClassInitializerMayBePostponed(ProgramMethod method) {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index a763db6..5be58f0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -137,11 +137,6 @@
}
@Override
- public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
- method.getMutableOptimizationInfo().markCheckNullReceiverBeforeAnySideEffect(mark);
- }
-
- @Override
public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
// Ignored.
}
@@ -234,12 +229,6 @@
}
@Override
- public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
- withMutableMethodOptimizationInfo(
- method, MutableMethodOptimizationInfo::unsetCheckNullReceiverBeforeAnySideEffect);
- }
-
- @Override
public void unsetClassInitializerMayBePostponed(ProgramMethod method) {
withMutableMethodOptimizationInfo(
method, MutableMethodOptimizationInfo::unsetClassInitializerMayBePostponed);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index 677ccdd..958b02d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -43,14 +43,12 @@
public static InliningIRProvider getThrowingInstance() {
return new InliningIRProvider() {
@Override
- public IRCode getInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFramesIfNpe) {
+ public IRCode getInliningIR(InvokeMethod invoke, ProgramMethod method) {
throw new Unreachable();
}
@Override
- public IRCode getAndCacheInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFrameIfThrowingNpe) {
+ public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
throw new Unreachable();
}
@@ -76,24 +74,23 @@
};
}
- public IRCode getInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFramesIfNpe) {
+ public IRCode getInliningIR(InvokeMethod invoke, ProgramMethod method) {
IRCode cached = cache.remove(invoke);
if (cached != null) {
return cached;
}
- Position position = Position.getPositionForInlining(appView, invoke, context);
- if (removeInnerFramesIfNpe) {
- position = position.builderWithCopy().setRemoveInnerFramesIfThrowingNpe(true).build();
- }
Origin origin = method.getOrigin();
return method.buildInliningIR(
- context, appView, valueNumberGenerator, position, origin, methodProcessor);
+ context,
+ appView,
+ valueNumberGenerator,
+ Position.getPositionForInlining(appView, invoke, context),
+ origin,
+ methodProcessor);
}
- public IRCode getAndCacheInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFrameIfThrowingNpe) {
- IRCode inliningIR = getInliningIR(invoke, method, removeInnerFrameIfThrowingNpe);
+ public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
+ IRCode inliningIR = getInliningIR(invoke, method);
cacheInliningIR(invoke, inliningIR);
return inliningIR;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
deleted file mode 100644
index 30b0da1..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ /dev/null
@@ -1,827 +0,0 @@
-// Copyright (c) 2018, 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.staticizer;
-
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndField;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-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.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Predicate;
-
-public final class ClassStaticizer {
-
- private final AppView<AppInfoWithLiveness> appView;
- private final DexItemFactory factory;
- private final IRConverter converter;
-
- private GraphLens graphLensForOptimizationPass;
-
- // Represents a staticizing candidate with all information
- // needed for staticizing.
- final class CandidateInfo {
- final DexProgramClass candidate;
- final DexEncodedField singletonField;
- final AtomicBoolean preserveRead = new AtomicBoolean(false);
- // Number of singleton field writes.
- final AtomicInteger fieldWrites = new AtomicInteger();
- // Number of instances created.
- final AtomicInteger instancesCreated = new AtomicInteger();
- final AtomicReference<DexEncodedMethod> constructor = new AtomicReference<>();
- final AtomicReference<DexEncodedMethod> getter = new AtomicReference<>();
-
- CandidateInfo(DexProgramClass candidate, DexEncodedField singletonField) {
- assert candidate != null;
- assert singletonField != null;
- this.candidate = candidate;
- this.singletonField = singletonField;
-
- // register itself
- candidates.put(candidate.type, this);
- }
-
- boolean isHostClassInitializer(ProgramMethod method) {
- return method.getDefinition().isClassInitializer() && method.getHolderType() == hostType();
- }
-
- DexType hostType() {
- return singletonField.getHolderType();
- }
-
- DexProgramClass hostClass() {
- DexProgramClass hostClass = asProgramClassOrNull(appView.definitionFor(hostType()));
- assert hostClass != null;
- return hostClass;
- }
-
- CandidateInfo invalidate() {
- candidates.remove(candidate.type);
- return null;
- }
- }
-
- final Map<CandidateInfo, LongLivedProgramMethodSetBuilder<?>> referencedFrom =
- new ConcurrentHashMap<>();
-
- private final Set<DexMethod> prunedMethods = Sets.newIdentityHashSet();
-
- // The map storing all the potential candidates for staticizing.
- final ConcurrentHashMap<DexType, CandidateInfo> candidates = new ConcurrentHashMap<>();
-
- public ClassStaticizer(AppView<AppInfoWithLiveness> appView, IRConverter converter) {
- this.appView = appView;
- this.factory = appView.dexItemFactory();
- this.converter = converter;
- }
-
- public void onMethodPruned(ProgramMethod method) {
- onMethodCodePruned(method);
- }
-
- public void onMethodCodePruned(ProgramMethod method) {
- prunedMethods.add(method.getReference());
- }
-
- public void prepareForPrimaryOptimizationPass(GraphLens graphLensForPrimaryOptimizationPass) {
- collectCandidates();
- this.graphLensForOptimizationPass = graphLensForPrimaryOptimizationPass;
- }
-
- public void prepareForSecondaryOptimizationPass(GraphLens graphLensForSecondaryOptimizationPass) {
- // Rewrite all the referenced from sets such that they are all rewritten up until the lens of
- // the second optimization pass. This is needed to ensure all elements in the referenced from
- // sets are rewritten up until the same graph lens, in case any referenced from sets are
- // extended during the secondary optimization pass.
- assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
- referencedFrom
- .values()
- .forEach(
- referencedFromBuilder ->
- referencedFromBuilder
- .removeAll(prunedMethods)
- .rewrittenWithLens(graphLensForSecondaryOptimizationPass));
- this.graphLensForOptimizationPass = graphLensForSecondaryOptimizationPass;
- prunedMethods.clear();
- }
-
- // Before doing any usage-based analysis we collect a set of classes that can be
- // candidates for staticizing. This analysis is very simple, but minimizes the
- // set of eligible classes staticizer tracks and thus time and memory it needs.
- public final void collectCandidates() {
- Set<DexType> notEligible = Sets.newIdentityHashSet();
- Map<DexType, DexEncodedField> singletonFields = new HashMap<>();
-
- assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
- appView
- .appInfo()
- .classes()
- .forEach(
- cls -> {
- // We only consider classes eligible for staticizing if there is just
- // one single static field in the whole app which has a type of this
- // class. This field will be considered to be a candidate for a singleton
- // field. The requirements for the initialization of this field will be
- // checked later.
- for (DexEncodedField field : cls.staticFields()) {
- DexType type = field.getReference().type;
- if (singletonFields.put(type, field) != null) {
- // There is already candidate singleton field found.
- markNotEligible(type, notEligible);
- }
- }
-
- // Don't allow fields with this candidate types.
- for (DexEncodedField field : cls.instanceFields()) {
- markNotEligible(field.getReference().type, notEligible);
- }
-
- // Don't allow methods that take a value of this type.
- for (DexEncodedMethod method : cls.methods()) {
- for (DexType parameter : method.getProto().parameters.values) {
- markNotEligible(parameter, notEligible);
- }
- if (method.isSynchronized()) {
- markNotEligible(cls.type, notEligible);
- }
- }
-
- // High-level limitations on what classes we consider eligible.
- if (cls.isInterface()
- // Must not be an interface or an abstract class.
- || cls.accessFlags.isAbstract()
- // Don't support candidates with instance fields
- || cls.instanceFields().size() > 0
- // Only support classes directly extending java.lang.Object
- || cls.superType != factory.objectType
- // The class must not have instantiated subtypes.
- || !cls.isEffectivelyFinal(appView)
- // Staticizing classes implementing interfaces is more
- // difficult, so don't support it until we really need it.
- || !cls.interfaces.isEmpty()) {
- markNotEligible(cls.type, notEligible);
- }
- });
-
- // Finalize the set of the candidates.
- appView
- .appInfo()
- .classes()
- .forEach(
- cls -> {
- DexType type = cls.type;
- if (!notEligible.contains(type)) {
- DexEncodedField field = singletonFields.get(type);
- if (field != null
- && // Singleton field found
- !field.accessFlags.isVolatile()
- && // Don't remove volatile fields.
- !isPinned(cls, field)) { // Don't remove pinned objects.
- assert field.accessFlags.isStatic();
- // Note: we don't check that the field is final, since we will analyze
- // later how and where it is initialized.
- new CandidateInfo(cls, field); // will self-register
- }
- }
- });
- }
-
- private void markNotEligible(DexType type, Set<DexType> notEligible) {
- if (type.isClassType()) {
- notEligible.add(type);
- }
- }
-
- private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
- AppInfoWithLiveness appInfo = appView.appInfo();
- if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.getReference())) {
- return true;
- }
- for (DexEncodedMethod method : clazz.methods()) {
- if (!method.isStatic() && appInfo.isPinned(method.getReference())) {
- return true;
- }
- }
- return false;
- }
-
- // Check staticizing candidates' usages to ensure the candidate can be staticized.
- //
- // The criteria for type CANDIDATE to be eligible for staticizing fall into
- // these categories:
- //
- // * checking that there is only one instance of the class created, and it is created
- // inside the host class initializer, and it is guaranteed that nobody can access this
- // field before it is assigned.
- //
- // * no other singleton field writes (except for those used to store the only candidate
- // class instance described above) are allowed.
- //
- // * values read from singleton field should only be used for instance method calls.
- //
- // NOTE: there are more criteria eligible class needs to satisfy to be actually staticized,
- // those will be checked later in staticizeCandidates().
- //
- // This method also collects all DexEncodedMethod instances that need to be rewritten if
- // appropriate candidate is staticized. Essentially anything that references instance method
- // or field defined in the class.
- //
- // NOTE: can be called concurrently.
- public final void examineMethodCode(IRCode code) {
- ProgramMethod context = code.context();
- Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
-
- CandidateInfo receiverClassCandidateInfo = candidates.get(context.getHolderType());
- Value receiverValue = code.getThis(); // NOTE: is null for static methods.
- if (receiverClassCandidateInfo != null) {
- if (receiverValue != null) {
- // We are inside an instance method of candidate class (not an instance initializer
- // which we will check later), check if all the references to 'this' are valid
- // (the call will invalidate the candidate if some of them are not valid).
- analyzeAllValueUsers(
- receiverClassCandidateInfo,
- receiverValue,
- factory.isConstructor(context.getReference()));
-
- // If the candidate is still valid, ignore all instructions
- // we treat as valid usages on receiver.
- if (candidates.get(context.getHolderType()) != null) {
- alreadyProcessed.addAll(receiverValue.uniqueUsers());
- }
- } else {
- // We are inside a static method of candidate class.
- // Check if this is a valid getter of the singleton field.
- if (context.getDefinition().returnType() == context.getHolderType()) {
- List<Instruction> examined = isValidGetter(receiverClassCandidateInfo, code);
- if (examined != null) {
- DexEncodedMethod getter = receiverClassCandidateInfo.getter.get();
- if (getter == null) {
- receiverClassCandidateInfo.getter.set(context.getDefinition());
- // Except for static-get and return, iterate other remaining instructions if any.
- alreadyProcessed.addAll(examined);
- } else {
- assert getter != context.getDefinition();
- // Not sure how to deal with many getters.
- receiverClassCandidateInfo.invalidate();
- }
- } else {
- // Invalidate the candidate if it has a static method whose return type is a candidate
- // type but doesn't return the singleton field (in a trivial way).
- receiverClassCandidateInfo.invalidate();
- }
- }
- }
- }
-
- // TODO(b/143375203): if fully implemented, the following iterator could be:
- // InstructionListIterator iterator = code.instructionListIterator();
- ListIterator<Instruction> iterator =
- Lists.newArrayList(code.instructionIterator()).listIterator();
- while (iterator.hasNext()) {
- Instruction instruction = iterator.next();
- if (alreadyProcessed.contains(instruction)) {
- continue;
- }
-
- if (instruction.isNewInstance()) {
- // Check the class being initialized against valid staticizing candidates.
- NewInstance newInstance = instruction.asNewInstance();
- CandidateInfo info = processInstantiation(context, iterator, newInstance);
- if (info != null) {
- alreadyProcessed.addAll(newInstance.outValue().aliasedUsers());
- // For host class initializers having eligible instantiation we also want to
- // ensure that the rest of the initializer consist of code w/o side effects.
- // This must guarantee that removing field access will not result in missing side
- // effects, otherwise we can still staticize, but cannot remove singleton reads.
- while (iterator.hasNext()) {
- if (!isAllowedInHostClassInitializer(context.getHolderType(), iterator.next(), code)) {
- info.preserveRead.set(true);
- iterator.previous();
- break;
- }
- // Ignore just read instruction.
- }
- addReferencedFrom(info, context);
- }
- continue;
- }
-
- if (instruction.isStaticPut()) {
- // Check the field being written to: no writes to singleton fields are allowed
- // except for those processed in processInstantiation(...).
- DexType candidateType = instruction.asStaticPut().getField().type;
- CandidateInfo candidateInfo = candidates.get(candidateType);
- if (candidateInfo != null) {
- candidateInfo.invalidate();
- }
- continue;
- }
-
- if (instruction.isStaticGet()) {
- // Check the field being read: make sure all usages are valid.
- CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
- if (info != null) {
- addReferencedFrom(info, context);
- // If the candidate is still valid, ignore all usages in further analysis.
- Value value = instruction.outValue();
- if (value != null) {
- alreadyProcessed.addAll(value.aliasedUsers());
- }
- }
- continue;
- }
-
- if (instruction.isInvokeStatic()) {
- // Check if it is a static singleton getter.
- CandidateInfo info = processInvokeStatic(instruction.asInvokeStatic());
- if (info != null) {
- addReferencedFrom(info, context);
- // If the candidate is still valid, ignore all usages in further analysis.
- Value value = instruction.outValue();
- if (value != null) {
- alreadyProcessed.addAll(value.aliasedUsers());
- }
- }
- continue;
- }
-
- if (instruction.isInvokeMethodWithReceiver()) {
- DexMethod invokedMethod = instruction.asInvokeMethodWithReceiver().getInvokedMethod();
- CandidateInfo candidateInfo = candidates.get(invokedMethod.holder);
- if (candidateInfo != null) {
- // A call to instance method of the candidate class we don't know how to deal with.
- candidateInfo.invalidate();
- }
- continue;
- }
-
- if (instruction.isInvokeCustom()) {
- // Just invalidate any candidates referenced from non-static context.
- CallSiteReferencesInvalidator invalidator =
- new CallSiteReferencesInvalidator(appView, context);
- invalidator.registerCallSite(instruction.asInvokeCustom().getCallSite());
- continue;
- }
-
- if (instruction.isInstanceGet() || instruction.isInstancePut()) {
- DexField fieldReferenced = instruction.asFieldInstruction().getField();
- CandidateInfo candidateInfo = candidates.get(fieldReferenced.holder);
- if (candidateInfo != null) {
- // Reads/writes to instance field of the candidate class are not supported.
- candidateInfo.invalidate();
- }
- continue;
- }
- }
- }
-
- private void addReferencedFrom(CandidateInfo info, ProgramMethod context) {
- GraphLens currentGraphLens = appView.graphLens();
- assert currentGraphLens == graphLensForOptimizationPass;
- LongLivedProgramMethodSetBuilder<?> builder =
- referencedFrom.computeIfAbsent(
- info,
- ignore ->
- LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet(currentGraphLens));
- builder.add(context);
- }
-
- private boolean isAllowedInHostClassInitializer(
- DexType host, Instruction insn, IRCode code) {
- return (insn.isStaticPut() && insn.asStaticPut().getField().holder == host) ||
- insn.isConstNumber() ||
- insn.isConstString() ||
- (insn.isGoto() && insn.asGoto().isTrivialGotoToTheNextBlock(code)) ||
- insn.isReturn();
- }
-
- private CandidateInfo processInstantiation(
- ProgramMethod context, ListIterator<Instruction> iterator, NewInstance newInstance) {
- DexType candidateType = newInstance.clazz;
- CandidateInfo candidateInfo = candidates.get(candidateType);
- if (candidateInfo == null) {
- return null; // Not interested.
- }
-
- if (iterator.previousIndex() != 0) {
- // Valid new instance must be the first instruction in the class initializer
- return candidateInfo.invalidate();
- }
-
- if (!candidateInfo.isHostClassInitializer(context)) {
- // A valid candidate must only have one instantiation which is
- // done in the static initializer of the host class.
- return candidateInfo.invalidate();
- }
-
- if (candidateInfo.instancesCreated.incrementAndGet() > 1) {
- // Only one instance must be ever created.
- return candidateInfo.invalidate();
- }
-
- Value candidateValue = newInstance.dest();
- if (candidateValue == null) {
- // Must be assigned to a singleton field.
- return candidateInfo.invalidate();
- }
-
- if (candidateValue.numberOfPhiUsers() > 0) {
- return candidateInfo.invalidate();
- }
-
- if (candidateValue.numberOfUsers() < 2) {
- // We expect two special users for each instantiation: constructor call and static field
- // write. We allow the instance to have other users as well, as long as they are valid
- // according to the user analysis.
- return candidateInfo.invalidate();
- }
-
- // Check usages. Currently we only support the patterns like:
- //
- // static constructor void <clinit>() {
- // new-instance v0, <candidate-type>
- // (opt) const/4 v1, #int 0 // (optional)
- // invoke-direct {v0, ...}, void <candidate-type>.<init>(...)
- // sput-object v0, <instance-field>
- // ...
- // ... // other usages that are valid according to the user analysis.
- //
- // In case we guarantee candidate constructor does not access <instance-field>
- // directly or indirectly we can guarantee that all the potential reads get
- // same non-null value.
-
- // Skip potential constant instructions
- while (iterator.hasNext() && isNonThrowingConstInstruction(iterator.next())) {
- // Intentionally empty.
- }
- iterator.previous();
- if (!iterator.hasNext()) {
- return candidateInfo.invalidate();
- }
- Set<Instruction> users = SetUtils.newIdentityHashSet(candidateValue.uniqueUsers());
- Instruction constructorCall = iterator.next();
- if (!isValidInitCall(candidateInfo, constructorCall, candidateValue, context)) {
- iterator.previous();
- return candidateInfo.invalidate();
- }
- boolean removedConstructorCall = users.remove(constructorCall);
- assert removedConstructorCall;
- if (!iterator.hasNext()) {
- return candidateInfo.invalidate();
- }
- Instruction staticPut = iterator.next();
- if (!isValidStaticPut(candidateInfo, staticPut)) {
- iterator.previous();
- return candidateInfo.invalidate();
- }
- boolean removedStaticPut = users.remove(staticPut);
- assert removedStaticPut;
- if (candidateInfo.fieldWrites.incrementAndGet() > 1) {
- return candidateInfo.invalidate();
- }
- if (!isSelectedValueUsersValid(candidateInfo, candidateValue, false, users)) {
- return candidateInfo.invalidate();
- }
- return candidateInfo;
- }
-
- private boolean isNonThrowingConstInstruction(Instruction instruction) {
- return instruction.isConstInstruction() && !instruction.instructionTypeCanThrow();
- }
-
- private boolean isValidInitCall(
- CandidateInfo info, Instruction instruction, Value candidateValue, ProgramMethod context) {
- if (!instruction.isInvokeDirect()) {
- return false;
- }
-
- // Check constructor.
- InvokeDirect invoke = instruction.asInvokeDirect();
- DexEncodedMethod methodInvoked =
- appView.appInfo().lookupDirectTarget(invoke.getInvokedMethod(), context);
- List<Value> values = invoke.inValues();
-
- if (ListUtils.lastIndexMatching(values, v -> v.getAliasedValue() == candidateValue) != 0
- || methodInvoked == null
- || methodInvoked.getHolderType() != info.candidate.type) {
- return false;
- }
-
- // Check arguments.
- for (int i = 1; i < values.size(); i++) {
- Value arg = values.get(i).getAliasedValue();
- if (arg.isPhi() || !arg.definition.isConstInstruction()) {
- return false;
- }
- }
-
- DexEncodedMethod previous = info.constructor.getAndSet(methodInvoked);
- assert previous == null;
- return true;
- }
-
- private boolean isValidStaticPut(CandidateInfo info, Instruction instruction) {
- if (!instruction.isStaticPut()) {
- return false;
- }
- // Allow single assignment to a singleton field.
- StaticPut staticPut = instruction.asStaticPut();
- DexClassAndField fieldAccessed = appView.appInfo().lookupStaticTarget(staticPut.getField());
- return fieldAccessed != null && fieldAccessed.getDefinition() == info.singletonField;
- }
-
- // Only allow a very trivial pattern: load the singleton field and return it, which looks like:
- //
- // v <- static-get singleton-field
- // <assume instructions on v> // (optional)
- // return v // or aliased value
- //
- // Returns a list of instructions that are examined (as long as the method is a trivial getter).
- private List<Instruction> isValidGetter(CandidateInfo info, IRCode code) {
- List<Instruction> instructions = new ArrayList<>();
- StaticGet staticGet = null;
- for (Instruction instr : code.instructions()) {
- if (instr.isStaticGet()) {
- staticGet = instr.asStaticGet();
- DexClassAndField fieldAccessed = appView.appInfo().lookupStaticTarget(staticGet.getField());
- if (fieldAccessed == null || fieldAccessed.getDefinition() != info.singletonField) {
- return null;
- }
- instructions.add(instr);
- continue;
- }
- if (instr.isAssume() || instr.isReturn()) {
- Value v = instr.inValues().get(0).getAliasedValue();
- if (v.isPhi() || v.definition != staticGet) {
- return null;
- }
- instructions.add(instr);
- continue;
- }
- // All other instructions are not allowed.
- return null;
- }
- return instructions;
- }
-
- // Static field get: can be a valid singleton field for a
- // candidate in which case we should check if all the usages of the
- // value read are eligible.
- private CandidateInfo processStaticFieldRead(StaticGet staticGet) {
- DexField field = staticGet.getField();
- DexType candidateType = field.type;
- CandidateInfo candidateInfo = candidates.get(candidateType);
- if (candidateInfo == null) {
- return null;
- }
-
- assert candidateInfo.singletonField
- == appView.appInfo().lookupStaticTarget(field).getDefinition()
- : "Added reference after collectCandidates(...)?";
-
- Value singletonValue = staticGet.dest();
- if (singletonValue != null) {
- candidateInfo = analyzeAllValueUsers(candidateInfo, singletonValue, false);
- }
- return candidateInfo;
- }
-
- // Static getter: if this invokes a registered getter, treat it as static field get.
- // That is, we should check if all the usages of the out value are eligible.
- private CandidateInfo processInvokeStatic(InvokeStatic invoke) {
- DexType candidateType = invoke.getInvokedMethod().proto.returnType;
- CandidateInfo candidateInfo = candidates.get(candidateType);
- if (candidateInfo == null) {
- return null;
- }
-
- if (invoke.hasOutValue()
- && candidateInfo.getter.get() != null
- && candidateInfo.getter.get().getReference() == invoke.getInvokedMethod()) {
- candidateInfo = analyzeAllValueUsers(candidateInfo, invoke.outValue(), false);
- }
- return candidateInfo;
- }
-
- private CandidateInfo analyzeAllValueUsers(
- CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
- assert value != null && value == value.getAliasedValue();
- if (value.numberOfPhiUsers() > 0) {
- return candidateInfo.invalidate();
- }
- if (!isSelectedValueUsersValid(
- candidateInfo, value, ignoreSuperClassInitInvoke, value.uniqueUsers())) {
- return candidateInfo.invalidate();
- }
- return candidateInfo;
- }
-
- private boolean isSelectedValueUsersValid(
- CandidateInfo candidateInfo,
- Value value,
- boolean ignoreSuperClassInitInvoke,
- Set<Instruction> currentUsers) {
- while (!currentUsers.isEmpty()) {
- Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
- for (Instruction user : currentUsers) {
- if (!isValidValueUser(
- candidateInfo, value, ignoreSuperClassInitInvoke, indirectUsers, user)) {
- return false;
- }
- }
- currentUsers = indirectUsers;
- }
- return true;
- }
-
- private boolean isValidValueUser(
- CandidateInfo candidateInfo,
- Value value,
- boolean ignoreSuperClassInitInvoke,
- Set<Instruction> indirectUsers,
- Instruction user) {
- if (user.isAssume()) {
- if (user.outValue().numberOfPhiUsers() > 0) {
- return false;
- }
- indirectUsers.addAll(user.outValue().uniqueUsers());
- return true;
- }
- if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
- InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
- Predicate<Value> isAliasedValue = v -> v.getAliasedValue() == value;
- DexMethod methodReferenced = invoke.getInvokedMethod();
- if (factory.isConstructor(methodReferenced)) {
- assert user.isInvokeDirect();
- if (ignoreSuperClassInitInvoke
- && ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
- && methodReferenced == factory.objectMembers.constructor) {
- // If we are inside candidate constructor and analyzing usages
- // of the receiver, we want to ignore invocations of superclass
- // constructor which will be removed after staticizing.
- return true;
- }
- return false;
- }
- AppInfoWithLiveness appInfo = appView.appInfo();
- MethodResolutionResult resolutionResult =
- appInfo.unsafeResolveMethodDueToDexFormat(methodReferenced);
- DexEncodedMethod methodInvoked =
- user.isInvokeDirect()
- ? resolutionResult.lookupInvokeDirectTarget(candidateInfo.candidate, appInfo)
- : resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
- if (ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
- && methodInvoked != null
- && methodInvoked.getHolderType() == candidateInfo.candidate.type) {
- return true;
- }
- }
-
- // All other users are not allowed.
- return false;
- }
-
- // Perform staticizing candidates:
- //
- // 1. After filtering candidates based on usage, finalize the list of candidates by
- // filtering out candidates which don't satisfy the requirements:
- //
- // * there must be one instance of the class
- // * constructor of the class used to create this instance must be a trivial one
- // * class initializer should only be present if candidate itself is own host
- // * no abstract or native instance methods
- //
- // 2. Rewrite instance methods of classes being staticized into static ones
- // 3. Rewrite methods referencing staticized members, also remove instance creation
- //
- public final void staticizeCandidates(
- OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
- new StaticizingProcessor(appView, this, converter).run(feedback, executorService);
- }
-
- private class CallSiteReferencesInvalidator extends UseRegistry<ProgramMethod> {
-
- CallSiteReferencesInvalidator(AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
- super(appView, context);
- }
-
- private void registerMethod(DexMethod method) {
- registerTypeReference(method.holder);
- registerProto(method.proto);
- }
-
- private void registerField(DexField field) {
- registerTypeReference(field.holder);
- registerTypeReference(field.type);
- }
-
- @Override
- public void registerInitClass(DexType clazz) {
- registerTypeReference(clazz);
- }
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {
- registerMethod(method);
- }
-
- @Override
- public void registerInvokeDirect(DexMethod method) {
- registerMethod(method);
- }
-
- @Override
- public void registerInvokeStatic(DexMethod method) {
- registerMethod(method);
- }
-
- @Override
- public void registerInvokeInterface(DexMethod method) {
- registerMethod(method);
- }
-
- @Override
- public void registerInvokeSuper(DexMethod method) {
- registerMethod(method);
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- registerField(field);
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {
- registerField(field);
- }
-
- @Override
- public void registerNewInstance(DexType type) {
- registerTypeReference(type);
- }
-
- @Override
- public void registerStaticFieldRead(DexField field) {
- registerField(field);
- }
-
- @Override
- public void registerStaticFieldWrite(DexField field) {
- registerField(field);
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- CandidateInfo candidateInfo = candidates.get(type);
- if (candidateInfo != null) {
- candidateInfo.invalidate();
- }
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- registerTypeReference(type);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
deleted file mode 100644
index efc5228..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2018, 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.staticizer;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.NestedGraphLens;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-
-class ClassStaticizerGraphLens extends NestedGraphLens {
-
- ClassStaticizerGraphLens(
- AppView<?> appView,
- BidirectionalOneToOneMap<DexField, DexField> fieldMapping,
- BidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping) {
- super(appView, fieldMapping, methodMapping, EMPTY_TYPE_MAP);
- }
-
- @Override
- protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
- if (methodMap.apply(originalMethod) == newMethod) {
- assert type == Type.VIRTUAL || type == Type.DIRECT;
- return Type.STATIC;
- }
- return super.mapInvocationType(newMethod, originalMethod, type);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
deleted file mode 100644
index 62176cb..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ /dev/null
@@ -1,897 +0,0 @@
-// Copyright (c) 2018, 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.staticizer;
-
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.utils.PredicateUtils.not;
-
-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.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMember;
-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.ProgramMethod;
-import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
-import com.android.tools.r8.ir.optimize.AssumeInserter;
-import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
-import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
-import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-// TODO(b/140766440): Use PostProcessor, instead of having its own post processing.
-final class StaticizingProcessor {
-
- private final AppView<AppInfoWithLiveness> appView;
- private final ClassStaticizer classStaticizer;
- private final IRConverter converter;
-
- private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.create();
-
- // Optimization order matters, hence a collection that preserves orderings.
- private final Map<DexEncodedMethod, ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>>
- processingQueue = new IdentityHashMap<>();
-
- private final ProgramMethodSet referencingExtraMethods = ProgramMethodSet.create();
- private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
- private final ProgramMethodSet methodsToBeStaticized = ProgramMethodSet.create();
- private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
- private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>();
- private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
-
- StaticizingProcessor(
- AppView<AppInfoWithLiveness> appView,
- ClassStaticizer classStaticizer,
- IRConverter converter) {
- this.appView = appView;
- this.classStaticizer = classStaticizer;
- this.converter = converter;
- }
-
- final void run(OptimizationFeedback feedback, ExecutorService executorService)
- throws ExecutionException {
- // Filter out candidates based on the information we collected while examining methods.
- Map<CandidateInfo, ProgramMethodSet> materializedReferencedFromCollections =
- finalEligibilityCheck();
-
- // Prepare interim data.
- prepareCandidates(materializedReferencedFromCollections);
-
- // Enqueue all host class initializers (only remove instantiations).
- ProgramMethodSet hostClassInitMethods = ProgramMethodSet.create();
- hostClassInits
- .values()
- .forEach(
- candidateInfo ->
- hostClassInitMethods.add(candidateInfo.hostClass().getProgramClassInitializer()));
- enqueueMethodsWithCodeOptimizations(
- hostClassInitMethods,
- optimizations ->
- optimizations
- .add(this::removeCandidateInstantiation)
- .add(this::insertAssumeInstructions)
- .add(collectOptimizationInfo(feedback)));
-
- // Enqueue instance methods to be staticized (only remove references to 'this'). Intentionally
- // not collecting optimization info for these methods, since they will be reprocessed again
- // below once staticized.
- enqueueMethodsWithCodeOptimizations(
- methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));
-
- // Rewrite outliner with lens.
- converter.outliner.rewriteWithLens();
-
- // Process queued methods with associated optimizations
- processMethodsConcurrently(feedback, executorService);
-
- // TODO(b/140767158): Merge the remaining part below.
- // Convert instance methods into static methods with an extra parameter.
- ProgramMethodSet methods = staticizeMethodSymbols();
-
- // Process all other methods that may reference singleton fields and call methods on them.
- // (Note that we exclude the former instance methods, but include new static methods created as
- // a result of staticizing.)
- methods.addAll(referencingExtraMethods);
- methods.addAll(hostClassInitMethods);
- enqueueMethodsWithCodeOptimizations(
- methods,
- optimizations ->
- optimizations
- .add(this::rewriteReferences)
- .add(this::insertAssumeInstructions)
- .add(collectOptimizationInfo(feedback)));
-
- // Rewrite outliner with lens.
- converter.outliner.rewriteWithLens();
-
- // Process queued methods with associated optimizations
- processMethodsConcurrently(feedback, executorService);
-
- // Clear all candidate information now that all candidates have been staticized.
- classStaticizer.candidates.clear();
- }
-
- private Map<CandidateInfo, ProgramMethodSet> finalEligibilityCheck() {
- Map<CandidateInfo, ProgramMethodSet> materializedReferencedFromCollections =
- new IdentityHashMap<>();
- Set<Phi> visited = Sets.newIdentityHashSet();
- Set<Phi> trivialPhis = Sets.newIdentityHashSet();
- Iterator<Entry<DexType, CandidateInfo>> it = classStaticizer.candidates.entrySet().iterator();
- while (it.hasNext()) {
- Entry<DexType, CandidateInfo> entry = it.next();
- DexType candidateType = entry.getKey();
- CandidateInfo info = entry.getValue();
- DexProgramClass candidateClass = info.candidate;
- DexType candidateHostType = info.hostType();
- DexEncodedMethod constructorUsed = info.constructor.get();
-
- int instancesCreated = info.instancesCreated.get();
- assert instancesCreated == info.fieldWrites.get();
- assert instancesCreated <= 1;
- assert (instancesCreated == 0) == (constructorUsed == null);
-
- // CHECK: One instance, one singleton field, known constructor
- if (instancesCreated == 0) {
- // Give up on the candidate, if there are any reads from instance
- // field the user should read null.
- it.remove();
- continue;
- }
-
- // CHECK: instance initializer used to create an instance is trivial.
- // NOTE: Along with requirement that candidate does not have instance
- // fields this should guarantee that the constructor is empty.
- assert candidateClass.instanceFields().size() == 0;
- assert constructorUsed.isProcessed();
- if (constructorUsed.getOptimizationInfo().mayHaveSideEffects()) {
- it.remove();
- continue;
- }
-
- // CHECK: class initializer should only be present if candidate itself is its own host.
- DexEncodedMethod classInitializer = candidateClass.getClassInitializer();
- assert classInitializer != null || candidateType != candidateHostType;
- if (classInitializer != null && candidateType != candidateHostType) {
- it.remove();
- continue;
- }
-
- // CHECK: no abstract or native instance methods.
- if (Streams.stream(candidateClass.methods()).anyMatch(
- method -> !method.isStatic() && (method.shouldNotHaveCode()))) {
- it.remove();
- continue;
- }
-
- // CHECK: references to 'this' in instance methods are fixable.
- TraversalContinuation fixableThisPointer =
- candidateClass.traverseProgramMethods(
- method -> {
- IRCode code = method.buildIR(appView);
- assert code != null;
- Value thisValue = code.getThis();
- assert thisValue != null;
- visited.clear();
- trivialPhis.clear();
- boolean onlyHasTrivialPhis =
- testAndCollectPhisComposedOfThis(
- visited, thisValue.uniquePhiUsers(), thisValue, trivialPhis);
- if (thisValue.hasPhiUsers() && !onlyHasTrivialPhis) {
- return TraversalContinuation.BREAK;
- }
- return TraversalContinuation.CONTINUE;
- },
- definition -> !definition.isStatic() && !definition.isInstanceInitializer());
- if (fixableThisPointer.shouldBreak()) {
- it.remove();
- continue;
- }
-
- ProgramMethodSet referencedFrom;
- if (classStaticizer.referencedFrom.containsKey(info)) {
- LongLivedProgramMethodSetBuilder<?> referencedFromBuilder =
- classStaticizer.referencedFrom.remove(info);
- assert referencedFromBuilder != null;
- referencedFrom =
- referencedFromBuilder
- .rewrittenWithLens(appView)
- .build(appView);
- materializedReferencedFromCollections.put(info, referencedFrom);
- } else {
- referencedFrom = ProgramMethodSet.empty();
- }
-
- // CHECK: references to field read usages are fixable.
- boolean fixableFieldReads = true;
- for (ProgramMethod method : referencedFrom) {
- IRCode code = method.buildIR(appView);
- assert code != null;
- List<Instruction> singletonUsers =
- Streams.stream(code.instructionIterator())
- .filter(
- instruction -> {
- if (instruction.isStaticGet()
- && instruction.asStaticGet().getField()
- == info.singletonField.getReference()) {
- return true;
- }
- DexEncodedMethod getter = info.getter.get();
- return getter != null
- && instruction.isInvokeStatic()
- && instruction.asInvokeStatic().getInvokedMethod()
- == getter.getReference();
- })
- .collect(Collectors.toList());
- boolean fixableFieldReadsPerUsage = true;
- for (Instruction user : singletonUsers) {
- if (user.outValue() == null) {
- continue;
- }
- Value dest = user.outValue();
- visited.clear();
- trivialPhis.clear();
- assert user.isInvokeStatic() || user.isStaticGet();
- DexMember member =
- user.isStaticGet()
- ? user.asStaticGet().getField()
- : user.asInvokeStatic().getInvokedMethod();
- boolean onlyHasTrivialPhis =
- testAndCollectPhisComposedOfSameMember(
- visited, dest.uniquePhiUsers(), member, trivialPhis);
- if (dest.hasPhiUsers() && !onlyHasTrivialPhis) {
- fixableFieldReadsPerUsage = false;
- break;
- }
- }
- if (!fixableFieldReadsPerUsage) {
- fixableFieldReads = false;
- break;
- }
- }
- if (!fixableFieldReads) {
- it.remove();
- continue;
- }
- }
- return materializedReferencedFromCollections;
- }
-
- private void prepareCandidates(
- Map<CandidateInfo, ProgramMethodSet> materializedReferencedFromCollections) {
- Set<DexEncodedMethod> removedInstanceMethods = Sets.newIdentityHashSet();
-
- for (CandidateInfo candidate : classStaticizer.candidates.values()) {
- DexProgramClass candidateClass = candidate.candidate;
- // Host class initializer
- DexClass hostClass = candidate.hostClass();
- DexEncodedMethod hostClassInitializer = hostClass.getClassInitializer();
- assert hostClassInitializer != null;
- CandidateInfo previous = hostClassInits.put(hostClassInitializer, candidate);
- assert previous == null;
-
- // Collect instance methods to be staticized.
- candidateClass.forEachProgramMethodMatching(
- definition -> {
- if (!definition.isStatic()) {
- removedInstanceMethods.add(definition);
- return !definition.isInstanceInitializer();
- }
- return false;
- },
- methodsToBeStaticized::add);
- singletonFields.put(candidate.singletonField.getReference(), candidate);
- DexEncodedMethod getter = candidate.getter.get();
- if (getter != null) {
- singletonGetters.put(getter.getReference(), candidate);
- }
- ProgramMethodSet referencedFrom =
- materializedReferencedFromCollections.getOrDefault(candidate, ProgramMethodSet.empty());
- assert validMethods(referencedFrom);
- referencingExtraMethods.addAll(referencedFrom);
- }
-
- removedInstanceMethods.forEach(referencingExtraMethods::remove);
- }
-
- private boolean validMethods(ProgramMethodSet referencedFrom) {
- for (ProgramMethod method : referencedFrom) {
- DexClass clazz = appView.definitionForHolder(method.getReference());
- assert clazz != null;
- assert clazz.lookupMethod(method.getReference()) == method.getDefinition();
- }
- return true;
- }
-
- private void enqueueMethodsWithCodeOptimizations(
- Iterable<ProgramMethod> methods,
- Consumer<ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>> extension) {
- for (ProgramMethod method : methods) {
- methodsToReprocess.add(method);
- extension.accept(
- processingQueue.computeIfAbsent(
- method.getDefinition(), ignore -> ImmutableList.builder()));
- }
- }
-
- /**
- * Processes the given methods concurrently using the given strategy.
- *
- * <p>Note that, when the strategy {@link #rewriteReferences(IRCode, MethodProcessor)} is being
- * applied, it is important that we never inline a method from `methods` which has still not been
- * reprocessed. This could lead to broken code, because the strategy that rewrites the broken
- * references is applied *before* inlining (because the broken references in the inlinee are never
- * rewritten). We currently avoid this situation by processing all the methods concurrently
- * (inlining of a method that is processed concurrently is not allowed).
- */
- private void processMethodsConcurrently(
- OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
- OneTimeMethodProcessor methodProcessor =
- OneTimeMethodProcessor.create(methodsToReprocess, appView);
- methodProcessor.forEachWaveWithExtension(
- (method, methodProcessingContext) ->
- forEachMethod(
- method,
- processingQueue.get(method.getDefinition()).build(),
- feedback,
- methodProcessor),
- executorService);
- // TODO(b/140767158): No need to clear if we can do every thing in one go.
- methodsToReprocess.clear();
- processingQueue.clear();
- }
-
- // TODO(b/140766440): Should be part or variant of PostProcessor.
- private void forEachMethod(
- ProgramMethod method,
- Collection<BiConsumer<IRCode, MethodProcessor>> codeOptimizations,
- OptimizationFeedback feedback,
- OneTimeMethodProcessor methodProcessor) {
- IRCode code = method.buildIR(appView);
- codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
- CodeRewriter.removeAssumeInstructions(appView, code);
- converter.removeDeadCodeAndFinalizeIR(code, feedback, Timing.empty());
- }
-
- private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) {
- AssumeInserter assumeInserter = converter.assumeInserter;
- if (assumeInserter != null) {
- assumeInserter.insertAssumeInstructions(code, Timing.empty());
- }
- }
-
- private BiConsumer<IRCode, MethodProcessor> collectOptimizationInfo(
- OptimizationFeedback feedback) {
- return (code, methodProcessor) ->
- converter.collectOptimizationInfo(
- code.context(),
- code,
- ClassInitializerDefaultsResult.empty(),
- feedback,
- methodProcessor,
- new MutableMethodConversionOptions(methodProcessor),
- BytecodeMetadataProvider.builder(),
- Timing.empty());
- }
-
- private void removeCandidateInstantiation(IRCode code, MethodProcessor methodProcessor) {
- CandidateInfo candidateInfo = hostClassInits.get(code.method());
- assert candidateInfo != null;
-
- // Find and remove instantiation and its users.
- for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
- if (newInstance.clazz == candidateInfo.candidate.type) {
- // Remove all usages
- // NOTE: requiring (a) the instance initializer to be trivial, (b) not allowing
- // candidates with instance fields and (c) requiring candidate to directly
- // extend java.lang.Object guarantees that the constructor is actually
- // empty and does not need to be inlined.
- assert candidateInfo.candidate.superType == factory().objectType;
- assert candidateInfo.candidate.instanceFields().size() == 0;
-
- Value singletonValue = newInstance.outValue();
- assert singletonValue != null;
-
- InvokeDirect uniqueConstructorInvoke =
- newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
- assert uniqueConstructorInvoke != null;
- uniqueConstructorInvoke.removeOrReplaceByDebugLocalRead(code);
-
- StaticPut uniqueStaticPut = null;
- for (Instruction user : singletonValue.uniqueUsers()) {
- if (user.isStaticPut()) {
- assert uniqueStaticPut == null;
- uniqueStaticPut = user.asStaticPut();
- }
- }
- assert uniqueStaticPut != null;
- uniqueStaticPut.removeOrReplaceByDebugLocalRead(code);
-
- if (newInstance.outValue().hasAnyUsers()) {
- TypeElement type = TypeElement.fromDexType(newInstance.clazz, maybeNull(), appView);
- newInstance.replace(
- new StaticGet(code.createValue(type), candidateInfo.singletonField.getReference()),
- code);
- } else {
- newInstance.removeOrReplaceByDebugLocalRead(code);
- }
- return;
- }
- }
-
- assert candidateInfo.singletonField.getOptimizationInfo().isDead()
- : "Must always be able to find and remove the instantiation";
- }
-
- private void removeReferencesToThis(IRCode code, MethodProcessor methodProcessor) {
- fixupStaticizedThisUsers(code, code.getThis());
- }
-
- private void rewriteReferences(IRCode code, MethodProcessor methodProcessor) {
- // Fetch all instructions that reference singletons to avoid concurrent modifications to the
- // instruction list that can arise from doing it directly in the iterator.
- List<Instruction> singletonUsers =
- Streams.stream(code.instructionIterator())
- .filter(
- instruction ->
- (instruction.isStaticGet()
- && singletonFields.containsKey(
- instruction.asFieldInstruction().getField()))
- || (instruction.isInvokeStatic()
- && singletonGetters.containsKey(
- instruction.asInvokeStatic().getInvokedMethod())))
- .collect(Collectors.toList());
- for (Instruction singletonUser : singletonUsers) {
- CandidateInfo candidateInfo;
- DexMember member;
- if (singletonUser.isStaticGet()) {
- candidateInfo = singletonFields.get(singletonUser.asStaticGet().getField());
- member = singletonUser.asStaticGet().getField();
- } else {
- assert singletonUser.isInvokeStatic();
- candidateInfo = singletonGetters.get(singletonUser.asInvokeStatic().getInvokedMethod());
- member = singletonUser.asInvokeStatic().getInvokedMethod();
- }
- Value value = singletonUser.outValue();
- if (value != null) {
- fixupStaticizedFieldUsers(code, value, member);
- }
- if (!candidateInfo.preserveRead.get()) {
- singletonUser.removeOrReplaceByDebugLocalRead(code);
- }
- }
- if (!candidateToHostMapping.isEmpty()) {
- remapMovedCandidates(code);
- }
- }
-
- private boolean testAndCollectPhisComposedOfThis(
- Set<Phi> visited, Set<Phi> phisToCheck, Value thisValue, Set<Phi> trivialPhis) {
- for (Phi phi : phisToCheck) {
- if (!visited.add(phi)) {
- continue;
- }
- Set<Phi> chainedPhis = Sets.newIdentityHashSet();
- for (Value operand : phi.getOperands()) {
- Value v = operand.getAliasedValue();
- if (v.isPhi()) {
- chainedPhis.add(operand.asPhi());
- } else {
- if (v != thisValue) {
- return false;
- }
- }
- }
- if (!chainedPhis.isEmpty()) {
- if (!testAndCollectPhisComposedOfThis(visited, chainedPhis, thisValue, trivialPhis)) {
- return false;
- }
- }
- trivialPhis.add(phi);
- }
- return true;
- }
-
- // Fixup `this` usages: rewrites all method calls so that they point to static methods.
- private void fixupStaticizedThisUsers(IRCode code, Value thisValue) {
- assert thisValue != null && thisValue == thisValue.getAliasedValue();
- // Depending on other optimizations, e.g., inlining, `this` can be flown to phis.
- Set<Phi> trivialPhis = Sets.newIdentityHashSet();
- boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfThis(
- Sets.newIdentityHashSet(), thisValue.uniquePhiUsers(), thisValue, trivialPhis);
- assert !thisValue.hasPhiUsers() || onlyHasTrivialPhis;
- assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
-
- Set<Instruction> users = SetUtils.newIdentityHashSet(thisValue.aliasedUsers());
- // If that is the case, method calls we want to fix up include users of those phis.
- for (Phi phi : trivialPhis) {
- users.addAll(phi.aliasedUsers());
- }
-
- fixupStaticizedValueUsers(code, users);
-
- // We can't directly use Phi#removeTrivialPhi because they still refer to different operands.
- trivialPhis.forEach(Phi::removeDeadPhi);
-
- // No matter what, number of phi users should be zero too.
- assert !thisValue.hasUsers() && !thisValue.hasPhiUsers();
- }
-
- // Re-processing finalized code may create slightly different IR code than what the examining
- // phase has seen. For example,
- //
- // b1:
- // s1 <- static-get singleton
- // ...
- // invoke-virtual { s1, ... } mtd1
- // goto Exit
- // b2:
- // s2 <- invoke-static getter()
- // ...
- // invoke-virtual { s2, ... } mtd1
- // goto Exit
- // ...
- // Exit: ...
- //
- // ~>
- //
- // b1:
- // s1 <- static-get singleton
- // ...
- // goto Exit
- // b2:
- // s2 <- invoke-static getter()
- // ...
- // goto Exit
- // Exit:
- // sp <- phi(s1, s2)
- // invoke-virtual { sp, ... } mtd1
- // ...
- //
- // From staticizer's viewpoint, `sp` is trivial in the sense that it is composed of values that
- // refer to the same singleton field. If so, we can safely relax the assertion; remove uses of
- // field reads; remove quasi-trivial phis; and then remove original field reads.
- private boolean testAndCollectPhisComposedOfSameMember(
- Set<Phi> visited, Set<Phi> phisToCheck, DexMember dexMember, Set<Phi> trivialPhis) {
- for (Phi phi : phisToCheck) {
- if (!visited.add(phi)) {
- continue;
- }
- Set<Phi> chainedPhis = Sets.newIdentityHashSet();
- for (Value operand : phi.getOperands()) {
- Value v = operand.getAliasedValue();
- if (v.isPhi()) {
- chainedPhis.add(operand.asPhi());
- } else {
- Instruction definition = v.definition;
- if (!definition.isStaticGet() && !definition.isInvokeStatic()) {
- return false;
- }
- if (definition.isStaticGet() && definition.asStaticGet().getField() != dexMember) {
- return false;
- } else if (definition.isInvokeStatic()
- && definition.asInvokeStatic().getInvokedMethod() != dexMember) {
- return false;
- }
- }
- }
- chainedPhis.addAll(phi.uniquePhiUsers());
- if (!chainedPhis.isEmpty()) {
- if (!testAndCollectPhisComposedOfSameMember(visited, chainedPhis, dexMember, trivialPhis)) {
- return false;
- }
- }
- trivialPhis.add(phi);
- }
- return true;
- }
-
- // Fixup field read usages. Same as {@link #fixupStaticizedThisUsers} except this one determines
- // quasi-trivial phis, based on the original field.
- private void fixupStaticizedFieldUsers(IRCode code, Value dest, DexMember member) {
- assert dest != null;
- // During the examine phase, field reads with any phi users have been invalidated, hence zero.
- // However, it may be not true if re-processing introduces phis after optimizing common suffix.
- Set<Phi> trivialPhis = Sets.newIdentityHashSet();
- boolean onlyHasTrivialPhis =
- testAndCollectPhisComposedOfSameMember(
- Sets.newIdentityHashSet(), dest.uniquePhiUsers(), member, trivialPhis);
- assert !dest.hasPhiUsers() || onlyHasTrivialPhis;
- assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
-
- Set<Instruction> users = SetUtils.newIdentityHashSet(dest.aliasedUsers());
- // If that is the case, method calls we want to fix up include users of those phis.
- for (Phi phi : trivialPhis) {
- users.addAll(phi.aliasedUsers());
- }
-
- fixupStaticizedValueUsers(code, users);
-
- // We can't directly use Phi#removeTrivialPhi because they still refer to different operands.
- trivialPhis.forEach(Phi::removeDeadPhi);
-
- // No matter what, number of phi users should be zero too.
- assert !dest.hasUsers() && !dest.hasPhiUsers();
- }
-
- private void fixupStaticizedValueUsers(IRCode code, Set<Instruction> users) {
- for (Instruction user : users) {
- if (user.isAssume()) {
- continue;
- }
- assert user.isInvokeVirtual() || user.isInvokeDirect();
- InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
- Value newValue = null;
- Value outValue = invoke.outValue();
- if (outValue != null) {
- newValue = code.createValue(outValue.getType());
- DebugLocalInfo localInfo = outValue.getLocalInfo();
- if (localInfo != null) {
- newValue.setLocalInfo(localInfo);
- }
- }
- List<Value> args = invoke.inValues();
- invoke.replace(
- new InvokeStatic(invoke.getInvokedMethod(), newValue, args.subList(1, args.size())),
- code);
- }
- }
-
- private void remapMovedCandidates(IRCode code) {
- InstructionListIterator it = code.instructionListIterator();
- while (it.hasNext()) {
- Instruction instruction = it.next();
-
- if (instruction.isStaticGet()) {
- StaticGet staticGet = instruction.asStaticGet();
- DexField field = mapFieldIfMoved(staticGet.getField());
- if (field != staticGet.getField()) {
- Value outValue = staticGet.dest();
- assert outValue != null;
- it.replaceCurrentInstruction(
- new StaticGet(
- code.createValue(
- TypeElement.fromDexType(
- field.type, outValue.getType().nullability(), appView),
- outValue.getLocalInfo()),
- field));
- }
- continue;
- }
-
- if (instruction.isStaticPut()) {
- StaticPut staticPut = instruction.asStaticPut();
- DexField field = mapFieldIfMoved(staticPut.getField());
- if (field != staticPut.getField()) {
- it.replaceCurrentInstruction(new StaticPut(staticPut.value(), field));
- }
- continue;
- }
-
- if (instruction.isInvokeStatic()) {
- InvokeStatic invoke = instruction.asInvokeStatic();
- DexMethod method = invoke.getInvokedMethod();
- DexType hostType = candidateToHostMapping.get(method.holder);
- if (hostType != null) {
- DexMethod newMethod = factory().createMethod(hostType, method.proto, method.name);
- Value outValue = invoke.outValue();
- DexType returnType = method.proto.returnType;
- Value newOutValue =
- returnType.isVoidType() || outValue == null
- ? null
- : code.createValue(
- TypeElement.fromDexType(
- returnType, outValue.getType().nullability(), appView),
- outValue.getLocalInfo());
- it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
- }
- continue;
- }
- }
- }
-
- private DexField mapFieldIfMoved(DexField field) {
- DexType hostType = candidateToHostMapping.get(field.holder);
- if (hostType != null) {
- field = factory().createField(hostType, field.type, field.name);
- }
- hostType = candidateToHostMapping.get(field.type);
- if (hostType != null) {
- field = factory().createField(field.holder, hostType, field.name);
- }
- return field;
- }
-
- private ProgramMethodSet staticizeMethodSymbols() {
- MutableBidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping =
- new BidirectionalOneToOneHashMap<>();
- MutableBidirectionalOneToOneMap<DexField, DexField> fieldMapping =
- new BidirectionalOneToOneHashMap<>();
-
- ProgramMethodSet staticizedMethods = ProgramMethodSet.create();
- for (CandidateInfo candidate : classStaticizer.candidates.values()) {
- DexProgramClass candidateClass = candidate.candidate;
-
- // Move instance methods into static ones.
- List<DexEncodedMethod> newDirectMethods = new ArrayList<>();
- for (DexEncodedMethod method : candidateClass.methods()) {
- if (method.isStatic()) {
- newDirectMethods.add(method);
- } else if (!factory().isConstructor(method.getReference())) {
- DexEncodedMethod staticizedMethod = method.toStaticMethodWithoutThis(appView);
- newDirectMethods.add(staticizedMethod);
- staticizedMethods.createAndAdd(candidateClass, staticizedMethod);
- methodMapping.put(method.getReference(), staticizedMethod.getReference());
- }
- }
- candidateClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
- candidateClass.setDirectMethods(newDirectMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
-
- // Consider moving static members from candidate into host.
- DexType hostType = candidate.hostType();
- if (candidateClass.type != hostType) {
- DexProgramClass hostClass = asProgramClassOrNull(appView.definitionFor(hostType));
- assert hostClass != null;
- if (!classMembersConflict(candidateClass, hostClass)
- && !hasMembersNotStaticized(candidateClass, staticizedMethods)) {
- // Move all members of the candidate class into host class.
- moveMembersIntoHost(staticizedMethods,
- candidateClass, hostType, hostClass, methodMapping, fieldMapping);
- }
- }
- }
-
- if (!methodMapping.isEmpty() || !fieldMapping.isEmpty()) {
- appView.setGraphLens(new ClassStaticizerGraphLens(appView, fieldMapping, methodMapping));
- }
- return staticizedMethods;
- }
-
- private boolean classMembersConflict(DexClass a, DexClass b) {
- assert Streams.stream(a.methods()).allMatch(DexEncodedMethod::isStatic);
- assert a.instanceFields().size() == 0;
- return a.staticFields().stream().anyMatch(fld -> b.lookupField(fld.getReference()) != null)
- || Streams.stream(a.methods())
- .anyMatch(method -> b.lookupMethod(method.getReference()) != null);
- }
-
- private boolean hasMembersNotStaticized(
- DexProgramClass candidateClass, ProgramMethodSet staticizedMethods) {
- // TODO(b/159174309): Refine the analysis to allow for fields.
- if (candidateClass.hasFields()) {
- return true;
- }
- // TODO(b/158018192): Activate again when picking up all references.
- return candidateClass.methods(not(staticizedMethods::contains)).iterator().hasNext();
- }
-
- private void moveMembersIntoHost(
- ProgramMethodSet staticizedMethods,
- DexProgramClass candidateClass,
- DexType hostType,
- DexProgramClass hostClass,
- MutableBidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping,
- MutableBidirectionalOneToOneMap<DexField, DexField> fieldMapping) {
- candidateToHostMapping.put(candidateClass.type, hostType);
-
- // Process static fields.
- int numOfHostStaticFields = hostClass.staticFields().size();
- DexEncodedField[] newFields =
- candidateClass.staticFields().size() > 0
- ? new DexEncodedField[numOfHostStaticFields + candidateClass.staticFields().size()]
- : new DexEncodedField[numOfHostStaticFields];
- List<DexEncodedField> oldFields = hostClass.staticFields();
- for (int i = 0; i < oldFields.size(); i++) {
- DexEncodedField field = oldFields.get(i);
- DexField newField = mapCandidateField(field.getReference(), candidateClass.type, hostType);
- if (newField != field.getReference()) {
- newFields[i] = field.toTypeSubstitutedField(appView, newField);
- fieldMapping.put(field.getReference(), newField);
- } else {
- newFields[i] = field;
- }
- }
- if (candidateClass.staticFields().size() > 0) {
- List<DexEncodedField> extraFields = candidateClass.staticFields();
- for (int i = 0; i < extraFields.size(); i++) {
- DexEncodedField field = extraFields.get(i);
- DexField newField = mapCandidateField(field.getReference(), candidateClass.type, hostType);
- if (newField != field.getReference()) {
- newFields[numOfHostStaticFields + i] = field.toTypeSubstitutedField(appView, newField);
- fieldMapping.put(field.getReference(), newField);
- } else {
- newFields[numOfHostStaticFields + i] = field;
- }
- }
- }
- hostClass.setStaticFields(newFields);
-
- // Process static methods.
- if (!candidateClass.getMethodCollection().hasDirectMethods()) {
- return;
- }
-
- Iterable<DexEncodedMethod> extraMethods = candidateClass.directMethods();
- List<DexEncodedMethod> newMethods =
- new ArrayList<>(candidateClass.getMethodCollection().numberOfDirectMethods());
- for (DexEncodedMethod method : extraMethods) {
- DexEncodedMethod newMethod =
- method.toTypeSubstitutedMethod(
- factory()
- .createMethod(hostType, method.getReference().proto, method.getReference().name));
- newMethods.add(newMethod);
- // If the old method from the candidate class has been staticized,
- if (staticizedMethods.remove(method)) {
- // Properly update staticized methods to reprocess, i.e., add the corresponding one that
- // has just been migrated to the host class.
- staticizedMethods.createAndAdd(hostClass, newMethod);
- }
- DexMethod originalMethod = methodMapping.getRepresentativeKey(method.getReference());
- if (originalMethod == null) {
- methodMapping.put(method.getReference(), newMethod.getReference());
- } else {
- methodMapping.put(originalMethod, newMethod.getReference());
- }
- }
- hostClass.addDirectMethods(newMethods);
- }
-
- private DexField mapCandidateField(DexField field, DexType candidateType, DexType hostType) {
- return field.holder != candidateType && field.type != candidateType ? field
- : factory().createField(
- field.holder == candidateType ? hostType : field.holder,
- field.type == candidateType ? hostType : field.type,
- field.name);
- }
-
- private DexItemFactory factory() {
- return appView.dexItemFactory();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 25667ea..89ac4ce 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -75,14 +75,17 @@
if (invokeType.isSuper() && options.canHaveSuperInvokeBug()) {
// To preserve semantics we should find the first library method on the boundary.
- DexClass libraryHolder =
- appView.definitionFor(
- firstLibraryClassOrFirstInterfaceTarget(
- resolutionResult.getResolvedHolder(),
- appView,
- resolvedMethod.getReference(),
- original.getHolderType(),
- DexClass::lookupMethod));
+ DexType firstLibraryTarget =
+ firstLibraryClassOrFirstInterfaceTarget(
+ resolutionResult.getResolvedHolder(),
+ appView,
+ resolvedMethod.getReference(),
+ original.getHolderType(),
+ DexClass::lookupMethod);
+ if (firstLibraryTarget == null) {
+ return original;
+ }
+ DexClass libraryHolder = appView.definitionFor(firstLibraryTarget);
if (libraryHolder == null) {
return original;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 4b8bff6..96b68f3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
+import com.android.tools.r8.optimize.argumentpropagation.unusedarguments.EffectivelyUnusedArgumentsAnalysis;
import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
@@ -44,6 +45,9 @@
*/
private ArgumentPropagatorCodeScanner codeScanner;
+ /** Collects and solves constraints for effectively unused argument removal. */
+ private EffectivelyUnusedArgumentsAnalysis effectivelyUnusedArgumentsAnalysis;
+
/**
* Analyzes the uses of arguments in methods to determine when reprocessing of methods will likely
* not lead to any additional code optimizations.
@@ -70,6 +74,7 @@
reprocessingCriteriaCollection = new ArgumentPropagatorReprocessingCriteriaCollection(appView);
codeScanner = new ArgumentPropagatorCodeScanner(appView, reprocessingCriteriaCollection);
+ effectivelyUnusedArgumentsAnalysis = new EffectivelyUnusedArgumentsAnalysis(appView);
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
@@ -83,12 +88,16 @@
// method state to unknown.
new ArgumentPropagatorUnoptimizableMethods(
appView, immediateSubtypingInfo, codeScanner.getMethodStates())
- .disableArgumentPropagationForUnoptimizableMethods(classes);
+ .initializeUnoptimizableMethodStates(classes);
// Compute the mapping from virtual methods to their root virtual method and the set of
// monomorphic virtual methods.
new VirtualRootMethodsAnalysis(appView, immediateSubtypingInfo)
- .extendVirtualRootMethods(classes, codeScanner);
+ .initializeVirtualRootMethods(classes, codeScanner);
+
+ // Find the virtual methods in the strongly connected component that only have monomorphic
+ // calls.
+ effectivelyUnusedArgumentsAnalysis.initializeOptimizableVirtualMethods(classes);
},
executorService);
@@ -103,10 +112,14 @@
assert methodProcessor.isPrimaryMethodProcessor();
codeScanner.scan(method, code, timing);
+ assert effectivelyUnusedArgumentsAnalysis != null;
+ effectivelyUnusedArgumentsAnalysis.scan(method, code);
+
assert reprocessingCriteriaCollection != null;
reprocessingCriteriaCollection.analyzeArgumentUses(method, code);
} else {
assert !methodProcessor.isPrimaryMethodProcessor();
+ assert effectivelyUnusedArgumentsAnalysis == null;
assert !methodProcessor.isPostMethodProcessor() || reprocessingCriteriaCollection == null;
}
}
@@ -210,6 +223,11 @@
interfaceDispatchOutsideProgram)
.populateOptimizationInfo(converter, executorService, timing);
timing.end();
+
+ timing.begin("Compute unused arguments");
+ effectivelyUnusedArgumentsAnalysis.computeEffectivelyUnusedArguments(codeScannerResult);
+ effectivelyUnusedArgumentsAnalysis = null;
+ timing.end();
}
/**
@@ -226,6 +244,9 @@
assert codeScanner != null;
MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
assert methodState == null || method.getDefinition().belongsToDirectPool();
+
+ assert effectivelyUnusedArgumentsAnalysis != null;
+ effectivelyUnusedArgumentsAnalysis.onMethodPruned(method);
}
public void onMethodCodePruned(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index 866fdd3..ae5322e 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -35,6 +35,11 @@
return new Builder(appView);
}
+ @Override
+ public boolean isArgumentPropagatorGraphLens() {
+ return true;
+ }
+
public boolean hasPrototypeChanges(DexMethod method) {
return prototypeChanges.containsKey(method);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
index 7ed397c..6319e90 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
@@ -36,7 +36,7 @@
// TODO(b/190154391): Consider if we should bail out for classes that inherit from a missing
// class.
- public void disableArgumentPropagationForUnoptimizableMethods(
+ public void initializeUnoptimizableMethodStates(
Collection<DexProgramClass> stronglyConnectedComponent) {
ProgramMethodSet unoptimizableVirtualMethods =
MethodOverridesCollector.findAllMethodsAndOverridesThatMatches(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
index 9f7434b..b9c09de 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
@@ -38,4 +38,9 @@
public int hashCode() {
return Objects.hash(method, index);
}
+
+ @Override
+ public String toString() {
+ return "MethodParameter(" + method + ", " + index + ")";
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
index ed2596a..d33cfb8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
@@ -114,7 +114,7 @@
super(appView, immediateSubtypingInfo);
}
- public void extendVirtualRootMethods(
+ public void initializeVirtualRootMethods(
Collection<DexProgramClass> stronglyConnectedComponent,
ArgumentPropagatorCodeScanner codeScanner) {
// Find all the virtual root methods in the strongly connected component.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/lenscoderewriter/NullCheckInserter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/lenscoderewriter/NullCheckInserter.java
new file mode 100644
index 0000000..3417bfa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/lenscoderewriter/NullCheckInserter.java
@@ -0,0 +1,201 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation.lenscoderewriter;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.FieldGet;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.WorkList;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public abstract class NullCheckInserter {
+
+ public static NullCheckInserter create(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCode code,
+ NonIdentityGraphLens graphLens,
+ GraphLens codeLens) {
+ NonIdentityGraphLens previousLens =
+ graphLens.find(lens -> lens.isArgumentPropagatorGraphLens() || lens == codeLens);
+ if (previousLens != null
+ && previousLens != codeLens
+ && previousLens.isArgumentPropagatorGraphLens()) {
+ return new NullCheckInserterImpl(appView.withLiveness(), code, graphLens);
+ }
+ return new EmptyNullCheckInserter();
+ }
+
+ public abstract void insertNullCheckForInvokeReceiverIfNeeded(
+ InvokeMethod invoke, InvokeMethod rewrittenInvoke, MethodLookupResult lookup);
+
+ public abstract void processWorklist();
+
+ static class NullCheckInserterImpl extends NullCheckInserter {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final IRCode code;
+ private final NonIdentityGraphLens graphLens;
+
+ private final Map<InvokeStatic, Value> worklist = new IdentityHashMap<>();
+
+ NullCheckInserterImpl(
+ AppView<AppInfoWithLiveness> appView, IRCode code, NonIdentityGraphLens graphLens) {
+ this.appView = appView;
+ this.code = code;
+ this.graphLens = graphLens;
+ }
+
+ @Override
+ public void insertNullCheckForInvokeReceiverIfNeeded(
+ InvokeMethod invoke, InvokeMethod rewrittenInvoke, MethodLookupResult lookup) {
+ // If the invoke has been staticized, then synthesize a null check for the receiver.
+ if (!invoke.isInvokeMethodWithReceiver() || !rewrittenInvoke.isInvokeStatic()) {
+ return;
+ }
+
+ ArgumentInfo receiverArgumentInfo =
+ lookup.getPrototypeChanges().getArgumentInfoCollection().getArgumentInfo(0);
+ if (!receiverArgumentInfo.isRemovedArgumentInfo()
+ || !receiverArgumentInfo.asRemovedArgumentInfo().isCheckNullOrZeroSet()) {
+ return;
+ }
+
+ Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+ TypeElement receiverType = receiver.getType();
+ if (receiverType.isDefinitelyNotNull()) {
+ return;
+ }
+
+ // A parameter with users is only subject to effectively unused argument removal if it is
+ // guaranteed to be non-null.
+ if (receiver.isDefinedByInstructionSatisfying(Instruction::isUnusedArgument)) {
+ return;
+ }
+
+ worklist.put(rewrittenInvoke.asInvokeStatic(), receiver);
+ }
+
+ @Override
+ public void processWorklist() {
+ if (worklist.isEmpty()) {
+ return;
+ }
+
+ BasicBlockIterator blockIterator = code.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (!instruction.isInvokeStatic()) {
+ continue;
+ }
+
+ InvokeStatic invoke = instruction.asInvokeStatic();
+ if (!worklist.containsKey(invoke)) {
+ continue;
+ }
+
+ // Don't insert null checks for effectively unread fields.
+ Value receiver = worklist.get(invoke);
+ if (isReadOfEffectivelyUnreadField(receiver)) {
+ continue;
+ }
+
+ instructionIterator.previous();
+
+ Position nullCheckPosition =
+ invoke
+ .getPosition()
+ .getOutermostCallerMatchingOrElse(
+ Position::isRemoveInnerFramesIfThrowingNpe, invoke.getPosition());
+ instructionIterator.insertNullCheckInstruction(
+ appView, code, blockIterator, receiver, nullCheckPosition);
+
+ // Reset the block iterator.
+ if (invoke.getBlock().hasCatchHandlers()) {
+ BasicBlock splitBlock = invoke.getBlock();
+ BasicBlock previousBlock = blockIterator.previousUntil(b -> b == splitBlock);
+ assert previousBlock == splitBlock;
+ blockIterator.next();
+ instructionIterator = splitBlock.listIterator(code);
+ }
+
+ Instruction next = instructionIterator.next();
+ assert next == invoke;
+ }
+ }
+ }
+
+ private boolean isReadOfEffectivelyUnreadField(Value value) {
+ if (value.isPhi()) {
+ boolean hasSeenReadOfEffectivelyUnreadField = false;
+ WorkList<Phi> reachablePhis = WorkList.newIdentityWorkList(value.asPhi());
+ while (reachablePhis.hasNext()) {
+ Phi currentPhi = reachablePhis.next();
+ for (Value operand : currentPhi.getOperands()) {
+ if (operand.isPhi()) {
+ reachablePhis.addIfNotSeen(operand.asPhi());
+ } else if (!isReadOfEffectivelyUnreadField(operand.getDefinition())) {
+ return false;
+ } else {
+ hasSeenReadOfEffectivelyUnreadField = true;
+ }
+ }
+ }
+ assert hasSeenReadOfEffectivelyUnreadField;
+ return true;
+ } else {
+ return isReadOfEffectivelyUnreadField(value.getDefinition());
+ }
+ }
+
+ private boolean isReadOfEffectivelyUnreadField(Instruction instruction) {
+ if (instruction.isFieldGet()) {
+ FieldGet fieldGet = instruction.asFieldGet();
+ DexField field = fieldGet.getField();
+ // This needs to map the field all the way to the final graph lens.
+ DexField rewrittenField = appView.graphLens().lookupField(field, graphLens);
+ FieldResolutionResult resolutionResult = appView.appInfo().resolveField(rewrittenField);
+ return resolutionResult.isSuccessfulResolution()
+ && !appView.appInfo().isFieldRead(resolutionResult.getResolvedField());
+ }
+ return false;
+ }
+ }
+
+ static class EmptyNullCheckInserter extends NullCheckInserter {
+
+ @Override
+ public void insertNullCheckForInvokeReceiverIfNeeded(
+ InvokeMethod invoke, InvokeMethod rewrittenInvoke, MethodLookupResult lookup) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void processWorklist() {
+ // Intentionally empty.
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
new file mode 100644
index 0000000..c1a322b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
@@ -0,0 +1,227 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation.unusedarguments;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Analysis to find arguments that are effectively unused. The analysis first computes the
+ * constraints for a given argument to be effectively unused, and then subsequently solves the
+ * computed constraints.
+ *
+ * <p>Example: Consider the following Companion class.
+ *
+ * <pre>
+ * static class Companion {
+ * void foo() {
+ * this.bar()
+ * }
+ * void bar() {
+ * doStuff();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>The analysis works as follows.
+ *
+ * <ol>
+ * <li>When IR processing the Companion.bar() method, the unused argument analysis records that
+ * its receiver is unused.
+ * <li>When IR processing the Companion.foo() method, the effectively unused argument analysis
+ * records that the receiver of Companion.foo() is unused if the receiver of Companion.bar()
+ * is unused.
+ * <li>After IR processing all methods, the effectively unused argument analysis builds a graph
+ * where there is a directed edge p0 -> p1 if the removal of method parameter p0 depends on
+ * the removal of method parameter p1.
+ * <li>The analysis then repeatedly removes method parameters from the graph that have no outgoing
+ * edges and marks such method parameters as being effectively unused.
+ * </ol>
+ *
+ * <p>
+ */
+public class EffectivelyUnusedArgumentsAnalysis {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ // Maps each method parameter p to the method parameters that must be effectively unused in order
+ // for the method parameter p to be effectively unused.
+ private final Map<MethodParameter, Set<MethodParameter>> constraints = new ConcurrentHashMap<>();
+
+ // Set of virtual methods that can definitely be optimized.
+ //
+ // We conservatively exclude virtual methods with dynamic dispatch from this set, since the
+ // parameters of such methods can only be removed if the same parameter can be removed from all
+ // other virtual methods with the same signature in the class hierarchy.
+ private final ProgramMethodSet optimizableVirtualMethods = ProgramMethodSet.createConcurrent();
+
+ public EffectivelyUnusedArgumentsAnalysis(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public void initializeOptimizableVirtualMethods(Set<DexProgramClass> stronglyConnectedComponent) {
+ // Group all virtual methods in this strongly connected component by their signature.
+ Map<DexMethodSignature, ProgramMethodSet> methodsBySignature = new HashMap<>();
+ for (DexProgramClass clazz : stronglyConnectedComponent) {
+ clazz.forEachProgramVirtualMethod(
+ method -> {
+ ProgramMethodSet methodsWithSameSignature =
+ methodsBySignature.computeIfAbsent(
+ method.getMethodSignature(), ignoreKey(ProgramMethodSet::create));
+ methodsWithSameSignature.add(method);
+ });
+ }
+ // Mark the unique method signatures as being optimizable.
+ methodsBySignature.forEach(
+ (signature, methodsWithSignature) -> {
+ if (methodsWithSignature.size() == 1) {
+ ProgramMethod method = methodsWithSignature.getFirst();
+ if (ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method)) {
+ optimizableVirtualMethods.add(method);
+ }
+ }
+ });
+ }
+
+ public void scan(ProgramMethod method, IRCode code) {
+ // If this method is not subject to optimization, then don't compute effectively unused
+ // constraints for the method parameters.
+ if (isUnoptimizable(method)) {
+ return;
+ }
+ Iterator<Argument> argumentIterator = code.argumentIterator();
+ while (argumentIterator.hasNext()) {
+ Argument argument = argumentIterator.next();
+ Value argumentValue = argument.outValue();
+ Set<MethodParameter> effectivelyUnusedConstraints =
+ computeEffectivelyUnusedConstraints(method, argument, argumentValue);
+ if (effectivelyUnusedConstraints != null && !effectivelyUnusedConstraints.isEmpty()) {
+ MethodParameter methodParameter =
+ new MethodParameter(method.getReference(), argument.getIndex());
+ assert !constraints.containsKey(methodParameter);
+ constraints.put(methodParameter, effectivelyUnusedConstraints);
+ }
+ }
+ }
+
+ private Set<MethodParameter> computeEffectivelyUnusedConstraints(
+ ProgramMethod method, Argument argument, Value argumentValue) {
+ if (method.getDefinition().isInstanceInitializer() && argumentValue.isThis()) {
+ return null;
+ }
+ if (method.getDefinition().willBeInlinedIntoInstanceInitializer(appView.dexItemFactory())) {
+ return null;
+ }
+ if (!ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, argument.getIndex())) {
+ return null;
+ }
+ if (!argumentValue.getType().isClassType()
+ || argumentValue.hasDebugUsers()
+ || argumentValue.hasPhiUsers()) {
+ return null;
+ }
+ Set<MethodParameter> effectivelyUnusedConstraints = new HashSet<>();
+ for (Instruction user : argumentValue.uniqueUsers()) {
+ if (user.isInvokeMethod()) {
+ InvokeMethod invoke = user.asInvokeMethod();
+ ProgramMethod resolvedMethod =
+ appView
+ .appInfo()
+ .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+ .getResolvedProgramMethod();
+ if (resolvedMethod == null || isUnoptimizable(resolvedMethod)) {
+ return null;
+ }
+ int dependentArgumentIndex =
+ ListUtils.uniqueIndexMatching(invoke.arguments(), value -> value == argumentValue);
+ if (dependentArgumentIndex < 0
+ || !ParameterRemovalUtils.canRemoveUnusedParameter(
+ appView, resolvedMethod, dependentArgumentIndex)) {
+ return null;
+ }
+ effectivelyUnusedConstraints.add(
+ new MethodParameter(resolvedMethod.getReference(), dependentArgumentIndex));
+ } else {
+ return null;
+ }
+ }
+ return effectivelyUnusedConstraints;
+ }
+
+ public void computeEffectivelyUnusedArguments(MethodStateCollectionByReference methodStates) {
+ // Build a graph where nodes are method parameters and there is an edge from method parameter p0
+ // to method parameter p1 if the removal of p0 depends on the removal of p1.
+ EffectivelyUnusedArgumentsGraph dependenceGraph =
+ EffectivelyUnusedArgumentsGraph.create(appView, constraints, methodStates);
+
+ // Remove all unoptimizable method parameters from the graph, as well as all nodes that depend
+ // on a node that is unoptimable.
+ dependenceGraph.removeUnoptimizableNodes();
+
+ // Repeatedly mark method parameters with no outgoing edges (i.e., no dependencies) as being
+ // unused.
+ WorkList<EffectivelyUnusedArgumentsGraphNode> worklist =
+ WorkList.newIdentityWorkList(dependenceGraph.getNodes());
+ while (!worklist.isEmpty()) {
+ while (!worklist.isEmpty()) {
+ EffectivelyUnusedArgumentsGraphNode node = worklist.removeSeen();
+ assert dependenceGraph.verifyContains(node);
+ node.removeUnusedSuccessors();
+ if (node.getSuccessors().isEmpty()) {
+ node.setUnused();
+ node.getPredecessors().forEach(worklist::addIfNotSeen);
+ node.cleanForRemoval();
+ dependenceGraph.remove(node);
+ }
+ }
+
+ // Handle mutually recursive methods. If there is a cycle p0 -> p1 -> ... -> pn -> p0 and each
+ // of the method parameters p0 ... pn has a unique successor, then remove the edge pn -> p0
+ // from the graph.
+ dependenceGraph.removeClosedCycles(worklist::addIfNotSeen);
+ }
+ }
+
+ private boolean isUnoptimizable(ProgramMethod method) {
+ if (method.getDefinition().belongsToDirectPool()) {
+ return !ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method);
+ }
+ if (optimizableVirtualMethods.contains(method)) {
+ assert ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method);
+ return false;
+ }
+ return true;
+ }
+
+ public void onMethodPruned(ProgramMethod method) {
+ for (int argumentIndex = 0;
+ argumentIndex < method.getDefinition().getNumberOfArguments();
+ argumentIndex++) {
+ constraints.remove(new MethodParameter(method.getReference(), argumentIndex));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
new file mode 100644
index 0000000..fdc01bc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
@@ -0,0 +1,183 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation.unusedarguments;
+
+import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.dfs.DFSStack;
+import com.android.tools.r8.utils.dfs.DFSWorklistItem;
+import com.android.tools.r8.utils.dfs.DFSWorklistItem.NewlyVisitedDFSWorklistItem;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+class EffectivelyUnusedArgumentsGraph {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ private final Map<MethodParameter, EffectivelyUnusedArgumentsGraphNode> nodes = new HashMap<>();
+
+ private EffectivelyUnusedArgumentsGraph(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public static EffectivelyUnusedArgumentsGraph create(
+ AppView<AppInfoWithLiveness> appView,
+ Map<MethodParameter, Set<MethodParameter>> constraints,
+ MethodStateCollectionByReference methodStates) {
+ EffectivelyUnusedArgumentsGraph graph = new EffectivelyUnusedArgumentsGraph(appView);
+ constraints.forEach(
+ (methodParameter, constraintsForMethodParameter) -> {
+ EffectivelyUnusedArgumentsGraphNode node = graph.getOrCreateNode(methodParameter);
+ for (MethodParameter constraint : constraintsForMethodParameter) {
+ graph.addConstraintEdge(node, constraint, constraints, methodStates);
+ }
+ });
+ return graph;
+ }
+
+ void addConstraintEdge(
+ EffectivelyUnusedArgumentsGraphNode node,
+ MethodParameter constraint,
+ Map<MethodParameter, Set<MethodParameter>> constraints,
+ MethodStateCollectionByReference methodStates) {
+ ProgramMethod dependencyMethod =
+ asProgramMethodOrNull(appView.definitionFor(constraint.getMethod()));
+ if (dependencyMethod == null) {
+ assert false;
+ node.setUnoptimizable();
+ return;
+ }
+
+ // A nullable method parameter cannot be effectively unused if it is used as the receiver in an
+ // invoke (or we cannot preserve NPE semantics).
+ if (dependencyMethod.getDefinition().isInstance()
+ && constraint.getIndex() == 0
+ && node.isNullable(methodStates)) {
+ node.setUnoptimizable();
+ return;
+ }
+
+ // If the successor parameter does not have any constraints, then the successor is not subject
+ // to effectively unused argument removal. In this case, the successor can only be removed if it
+ // is truly unused.
+ if (!constraints.containsKey(constraint)) {
+ MethodOptimizationInfo optimizationInfo = dependencyMethod.getOptimizationInfo();
+ if (!optimizationInfo.hasUnusedArguments()
+ || !optimizationInfo.getUnusedArguments().get(constraint.getIndex())) {
+ node.setUnoptimizable();
+ }
+ return;
+ }
+
+ EffectivelyUnusedArgumentsGraphNode successor = getOrCreateNode(constraint, dependencyMethod);
+ if (node != successor) {
+ node.addSuccessor(successor);
+ }
+ }
+
+ Collection<EffectivelyUnusedArgumentsGraphNode> getNodes() {
+ return nodes.values();
+ }
+
+ EffectivelyUnusedArgumentsGraphNode getOrCreateNode(MethodParameter parameter) {
+ ProgramMethod method = asProgramMethodOrNull(appView.definitionFor(parameter.getMethod()));
+ return method != null ? getOrCreateNode(parameter, method) : null;
+ }
+
+ EffectivelyUnusedArgumentsGraphNode getOrCreateNode(
+ MethodParameter parameter, ProgramMethod method) {
+ return nodes.computeIfAbsent(
+ parameter, p -> new EffectivelyUnusedArgumentsGraphNode(method, p.getIndex()));
+ }
+
+ void remove(EffectivelyUnusedArgumentsGraphNode node) {
+ assert node.getSuccessors().isEmpty();
+ assert node.getPredecessors().isEmpty();
+ MethodParameter methodParameter =
+ new MethodParameter(node.getMethod().getReference(), node.getArgumentIndex());
+ EffectivelyUnusedArgumentsGraphNode removed = nodes.remove(methodParameter);
+ assert removed == node;
+ }
+
+ void removeClosedCycles(Consumer<EffectivelyUnusedArgumentsGraphNode> reprocess) {
+ Set<EffectivelyUnusedArgumentsGraphNode> seen = Sets.newIdentityHashSet();
+ for (EffectivelyUnusedArgumentsGraphNode root : getNodes()) {
+ if (seen.contains(root)) {
+ continue;
+ }
+ DFSStack<EffectivelyUnusedArgumentsGraphNode> stack = DFSStack.createIdentityStack();
+ Deque<DFSWorklistItem<EffectivelyUnusedArgumentsGraphNode>> worklist = new ArrayDeque<>();
+ worklist.add(new NewlyVisitedDFSWorklistItem<>(root));
+ while (!worklist.isEmpty()) {
+ DFSWorklistItem<EffectivelyUnusedArgumentsGraphNode> item = worklist.removeLast();
+ stack.handle(item);
+ if (item.isFullyVisited()) {
+ continue;
+ }
+ EffectivelyUnusedArgumentsGraphNode node = item.getValue();
+ seen.add(node);
+ worklist.add(item.asNewlyVisited().toFullyVisited());
+ node.getSuccessors()
+ .removeIf(
+ successor -> {
+ if (stack.contains(successor)) {
+ Deque<EffectivelyUnusedArgumentsGraphNode> cycle =
+ stack.getCycleStartingAt(successor);
+ boolean isClosedCycle =
+ Iterables.all(cycle, member -> member.getSuccessors().size() == 1);
+ if (isClosedCycle) {
+ // Remove edge and reprocess this node, since it is now eligible for unused
+ // argument removal.
+ boolean removed = successor.getPredecessors().remove(node);
+ assert removed;
+ reprocess.accept(node);
+ // Return true to remove the successor from the successors of the current
+ // node (to prevent ConcurrentModificationException).
+ return true;
+ }
+ } else {
+ worklist.add(new NewlyVisitedDFSWorklistItem<>(successor));
+ }
+ return false;
+ });
+ }
+ }
+ }
+
+ boolean verifyContains(EffectivelyUnusedArgumentsGraphNode node) {
+ MethodParameter methodParameter =
+ new MethodParameter(node.getMethod().getReference(), node.getArgumentIndex());
+ return nodes.containsKey(methodParameter);
+ }
+
+ void removeUnoptimizableNodes() {
+ WorkList<EffectivelyUnusedArgumentsGraphNode> worklist = WorkList.newIdentityWorkList();
+ for (EffectivelyUnusedArgumentsGraphNode node : getNodes()) {
+ if (node.isUnoptimizable()) {
+ worklist.addIfNotSeen(node);
+ }
+ }
+ while (worklist.hasNext()) {
+ EffectivelyUnusedArgumentsGraphNode node = worklist.next();
+ worklist.addIfNotSeen(node.getPredecessors());
+ node.cleanForRemoval();
+ remove(node);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraphNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraphNode.java
new file mode 100644
index 0000000..2b52686
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraphNode.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation.unusedarguments;
+
+import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.google.common.collect.Sets;
+import java.util.BitSet;
+import java.util.Set;
+
+class EffectivelyUnusedArgumentsGraphNode {
+
+ private final ProgramMethod method;
+ private final int argumentIndex;
+
+ private final Set<EffectivelyUnusedArgumentsGraphNode> predecessors = Sets.newIdentityHashSet();
+ private final Set<EffectivelyUnusedArgumentsGraphNode> successors = Sets.newIdentityHashSet();
+
+ private boolean unoptimizable;
+
+ EffectivelyUnusedArgumentsGraphNode(ProgramMethod method, int argumentIndex) {
+ this.method = method;
+ this.argumentIndex = argumentIndex;
+ }
+
+ void addSuccessor(EffectivelyUnusedArgumentsGraphNode node) {
+ if (successors.add(node)) {
+ node.predecessors.add(this);
+ } else {
+ assert node.predecessors.contains(this);
+ }
+ }
+
+ void cleanForRemoval() {
+ clearSuccessors();
+ clearPredecessors();
+ }
+
+ void clearSuccessors() {
+ for (EffectivelyUnusedArgumentsGraphNode successor : successors) {
+ boolean removed = successor.predecessors.remove(this);
+ assert removed;
+ }
+ successors.clear();
+ }
+
+ void clearPredecessors() {
+ for (EffectivelyUnusedArgumentsGraphNode predecessor : predecessors) {
+ boolean removed = predecessor.successors.remove(this);
+ assert removed;
+ }
+ predecessors.clear();
+ }
+
+ public int getArgumentIndex() {
+ return argumentIndex;
+ }
+
+ public ProgramMethod getMethod() {
+ return method;
+ }
+
+ Set<EffectivelyUnusedArgumentsGraphNode> getPredecessors() {
+ return predecessors;
+ }
+
+ Set<EffectivelyUnusedArgumentsGraphNode> getSuccessors() {
+ return successors;
+ }
+
+ boolean isNullable(MethodStateCollectionByReference methodStates) {
+ if (method.getDefinition().isInstance() && argumentIndex == 0) {
+ return false;
+ }
+ MethodState methodState = methodStates.get(method);
+ if (methodState.isBottom()) {
+ // TODO: this means the method is unreachable? what to do in this case?
+ return true;
+ }
+ assert !methodState.isBottom();
+ if (methodState.isUnknown()) {
+ return true;
+ }
+ assert methodState.isMonomorphic();
+ ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
+ ParameterState parameterState = monomorphicMethodState.getParameterState(argumentIndex);
+ if (parameterState.isUnknown()) {
+ return true;
+ }
+ assert parameterState.isConcrete();
+ assert parameterState.asConcrete().isReferenceParameter();
+ return parameterState.asConcrete().asReferenceParameter().getNullability().isMaybeNull();
+ }
+
+ boolean isUnoptimizable() {
+ return unoptimizable;
+ }
+
+ boolean isUnused() {
+ return method.getOptimizationInfo().hasUnusedArguments()
+ && method.getOptimizationInfo().getUnusedArguments().get(argumentIndex);
+ }
+
+ void removeUnusedSuccessors() {
+ successors.removeIf(
+ successor -> {
+ if (successor.isUnused()) {
+ boolean removed = successor.predecessors.remove(this);
+ assert removed;
+ return true;
+ }
+ return false;
+ });
+ }
+
+ void setUnoptimizable() {
+ unoptimizable = true;
+ }
+
+ void setUnused() {
+ if (method.getOptimizationInfo().hasUnusedArguments()) {
+ getSimpleFeedback()
+ .fixupUnusedArguments(method, unusedArguments -> unusedArguments.set(argumentIndex));
+ } else {
+ BitSet unusedArguments = new BitSet(method.getDefinition().getNumberOfArguments());
+ unusedArguments.set(argumentIndex);
+ getSimpleFeedback().setUnusedArguments(method, unusedArguments);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
index 72cbfba..72129db 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
@@ -22,7 +22,8 @@
if (appView.appInfo().isKeepUnusedArgumentsMethod(method)) {
return false;
}
- return !appView.appInfoWithLiveness().isMethodTargetedByInvokeDynamic(method);
+ return method.getDefinition().isLibraryMethodOverride().isFalse()
+ && !appView.appInfoWithLiveness().isMethodTargetedByInvokeDynamic(method);
}
public static boolean canRemoveUnusedParameter(
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
new file mode 100644
index 0000000..c82f332
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -0,0 +1,242 @@
+// Copyright (c) 2022, 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.utils;
+
+import static com.android.tools.r8.utils.DepthFirstSearchWorkListBase.ProcessingState.FINISHED;
+import static com.android.tools.r8.utils.DepthFirstSearchWorkListBase.ProcessingState.NOT_PROCESSED;
+import static com.android.tools.r8.utils.DepthFirstSearchWorkListBase.ProcessingState.WAITING;
+
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.DFSNodeImpl;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+public abstract class DepthFirstSearchWorkListBase<N, T extends DFSNodeImpl<N>> {
+
+ public interface DFSNode<N> {
+ N getNode();
+
+ boolean seenAndNotProcessed();
+ }
+
+ public interface DFSNodeWithState<N, S> extends DFSNode<N> {
+
+ S getState();
+
+ void setState(S backtrackState);
+
+ boolean hasState();
+ }
+
+ enum ProcessingState {
+ NOT_PROCESSED,
+ WAITING,
+ FINISHED;
+ }
+
+ static class DFSNodeImpl<N> implements DFSNode<N> {
+
+ private final N node;
+ private ProcessingState processingState = NOT_PROCESSED;
+
+ private DFSNodeImpl(N node) {
+ this.node = node;
+ }
+
+ boolean isNotProcessed() {
+ return processingState == NOT_PROCESSED;
+ }
+
+ boolean isFinished() {
+ return processingState == FINISHED;
+ }
+
+ void setWaiting() {
+ processingState = WAITING;
+ }
+
+ void setFinished() {
+ assert processingState != FINISHED;
+ processingState = FINISHED;
+ }
+
+ @Override
+ public N getNode() {
+ return node;
+ }
+
+ @Override
+ public boolean seenAndNotProcessed() {
+ return processingState == WAITING;
+ }
+ }
+
+ static class DFSNodeWithStateImpl<N, S> extends DFSNodeImpl<N> implements DFSNodeWithState<N, S> {
+
+ private S state;
+
+ private DFSNodeWithStateImpl(N node) {
+ super(node);
+ }
+
+ @Override
+ public S getState() {
+ return state;
+ }
+
+ @Override
+ public void setState(S state) {
+ this.state = state;
+ }
+
+ @Override
+ public boolean hasState() {
+ return state != null;
+ }
+ }
+
+ private final ArrayDeque<T> workList = new ArrayDeque<>();
+
+ abstract T createDfsNode(N node);
+
+ /** The initial processing of a node during forward search */
+ abstract TraversalContinuation internalOnVisit(T node);
+
+ /** The joining of state during backtracking of the algorithm. */
+ abstract TraversalContinuation internalOnJoin(T node);
+
+ final T internalEnqueueNode(N value) {
+ T dfsNode = createDfsNode(value);
+ if (dfsNode.isNotProcessed()) {
+ workList.addLast(dfsNode);
+ }
+ return dfsNode;
+ }
+
+ @SafeVarargs
+ public final TraversalContinuation run(N... roots) {
+ return run(Arrays.asList(roots));
+ }
+
+ public final TraversalContinuation run(Collection<N> roots) {
+ roots.forEach(this::internalEnqueueNode);
+ TraversalContinuation continuation = TraversalContinuation.CONTINUE;
+ while (!workList.isEmpty()) {
+ T node = workList.removeLast();
+ if (node.isFinished()) {
+ continue;
+ }
+ if (node.isNotProcessed()) {
+ workList.addLast(node);
+ node.setWaiting();
+ continuation = internalOnVisit(node);
+ } else {
+ assert node.seenAndNotProcessed();
+ continuation = internalOnJoin(node);
+ node.setFinished();
+ }
+ if (continuation.shouldBreak()) {
+ return continuation;
+ }
+ }
+ return continuation;
+ }
+
+ public abstract static class DepthFirstSearchWorkList<N>
+ extends DepthFirstSearchWorkListBase<N, DFSNodeImpl<N>> {
+
+ /**
+ * The initial processing of the node when visiting the first time during the depth first
+ * search.
+ *
+ * @param node The current node.
+ * @param childNodeConsumer A consumer for adding child nodes. If an element has been seen
+ * before but not finished there is a cycle.
+ * @return A value describing if the DFS algorithm should continue to run.
+ */
+ protected abstract TraversalContinuation process(
+ DFSNode<N> node, Function<N, DFSNode<N>> childNodeConsumer);
+
+ @Override
+ DFSNodeImpl<N> createDfsNode(N node) {
+ return new DFSNodeImpl<>(node);
+ }
+
+ @Override
+ TraversalContinuation internalOnVisit(DFSNodeImpl<N> node) {
+ return process(node, this::internalEnqueueNode);
+ }
+
+ @Override
+ protected TraversalContinuation internalOnJoin(DFSNodeImpl<N> node) {
+ return TraversalContinuation.CONTINUE;
+ }
+ }
+
+ public abstract static class StatefulDepthFirstSearchWorkList<N, S>
+ extends DepthFirstSearchWorkListBase<N, DFSNodeWithStateImpl<N, S>> {
+
+ private final Map<DFSNodeWithStateImpl<N, S>, List<DFSNodeWithState<N, S>>> childStateMap =
+ new IdentityHashMap<>();
+
+ /**
+ * The initial processing of the node when visiting the first time during the depth first
+ * search.
+ *
+ * @param node The current node.
+ * @param childNodeConsumer A consumer for adding child nodes. If an element has been seen
+ * before but not finished there is a cycle.
+ * @return A value describing if the DFS algorithm should continue to run.
+ */
+ protected abstract TraversalContinuation process(
+ DFSNodeWithState<N, S> node, Function<N, DFSNodeWithState<N, S>> childNodeConsumer);
+
+ /**
+ * The joining of state during backtracking of the algorithm.
+ *
+ * @param node The current node
+ * @param childStates The already computed child states.
+ * @return A value describing if the DFS algorithm should continue to run.
+ */
+ protected abstract TraversalContinuation joiner(
+ DFSNodeWithState<N, S> node, List<DFSNodeWithState<N, S>> childStates);
+
+ @Override
+ DFSNodeWithStateImpl<N, S> createDfsNode(N node) {
+ return new DFSNodeWithStateImpl<>(node);
+ }
+
+ @Override
+ TraversalContinuation internalOnVisit(DFSNodeWithStateImpl<N, S> node) {
+ List<DFSNodeWithState<N, S>> childStates = new ArrayList<>();
+ List<DFSNodeWithState<N, S>> removedChildStates = childStateMap.put(node, childStates);
+ assert removedChildStates == null;
+ return process(
+ node,
+ successor -> {
+ DFSNodeWithStateImpl<N, S> successorNode = internalEnqueueNode(successor);
+ childStates.add(successorNode);
+ return successorNode;
+ });
+ }
+
+ @Override
+ protected TraversalContinuation internalOnJoin(DFSNodeWithStateImpl<N, S> node) {
+ return joiner(
+ node,
+ childStateMap.computeIfAbsent(
+ node,
+ n -> {
+ assert false : "Unexpected joining of not visited node";
+ return new ArrayList<>();
+ }));
+ }
+ }
+}
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 d1264f6..d32fe5c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -241,7 +241,6 @@
public void disableGlobalOptimizations() {
inlinerOptions.enableInlining = false;
enableClassInlining = false;
- enableClassStaticizer = false;
enableDevirtualization = false;
enableVerticalClassMerging = false;
enableEnumUnboxing = false;
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 cd62dc2..9c6e2a7 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -279,6 +279,21 @@
assert verifyComparatorOnSortedList(items, comparator);
}
+ public static <T> int uniqueIndexMatching(List<T> list, Predicate<T> predicate) {
+ int result = -1;
+ for (int i = 0; i < list.size(); i++) {
+ T element = list.get(i);
+ if (predicate.test(element)) {
+ if (result == -1) {
+ result = i;
+ } else {
+ return -1;
+ }
+ }
+ }
+ return result;
+ }
+
private static <T> boolean verifyComparatorOnSortedList(List<T> items, Comparator<T> comparator) {
for (int i = 0; i < items.size(); i++) {
boolean allowEqual = true;
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 3177689..79c2ed7 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -92,6 +92,18 @@
return false;
}
+ public boolean addFirstIfNotSeen(T item) {
+ if (seen.add(item)) {
+ workingList.addFirst(item);
+ return true;
+ }
+ return false;
+ }
+
+ public void addFirstIgnoringSeenSet(T item) {
+ workingList.addFirst(item);
+ }
+
public boolean hasNext() {
return !workingList.isEmpty();
}
@@ -117,6 +129,12 @@
return workingList.removeFirst();
}
+ public T removeSeen() {
+ T next = next();
+ seen.remove(next);
+ return next;
+ }
+
public Set<T> getSeenSet() {
return Collections.unmodifiableSet(seen);
}
diff --git a/src/main/java/com/android/tools/r8/utils/dfs/DFSStack.java b/src/main/java/com/android/tools/r8/utils/dfs/DFSStack.java
new file mode 100644
index 0000000..635d9a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/dfs/DFSStack.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2022, 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.utils.dfs;
+
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.Set;
+
+public class DFSStack<T> {
+
+ private final Deque<T> stack;
+ private final Set<T> stackSet;
+
+ private DFSStack(Deque<T> stack, Set<T> stackSet) {
+ this.stack = stack;
+ this.stackSet = stackSet;
+ }
+
+ public static <T> DFSStack<T> createIdentityStack() {
+ return new DFSStack<>(new ArrayDeque<>(), Sets.newIdentityHashSet());
+ }
+
+ public boolean contains(T item) {
+ return stackSet.contains(item);
+ }
+
+ public Deque<T> getCycleStartingAt(T entry) {
+ Deque<T> cycle = new ArrayDeque<>();
+ do {
+ assert !stack.isEmpty();
+ cycle.addLast(stack.removeLast());
+ } while (cycle.getLast() != entry);
+ recoverStack(cycle);
+ return cycle;
+ }
+
+ public void handle(DFSWorklistItem<T> item) {
+ if (item.isNewlyVisited()) {
+ push(item.getValue());
+ } else {
+ assert item.isFullyVisited();
+ pop(item.getValue());
+ }
+ }
+
+ public void pop(T expectedItem) {
+ T popped = stack.removeLast();
+ assert popped == expectedItem;
+ boolean removed = stackSet.remove(popped);
+ assert removed;
+ }
+
+ public void push(T item) {
+ stack.addLast(item);
+ boolean added = stackSet.add(item);
+ assert added;
+ }
+
+ private void recoverStack(Deque<T> extractedCycle) {
+ Iterator<T> descendingIt = extractedCycle.descendingIterator();
+ while (descendingIt.hasNext()) {
+ stack.addLast(descendingIt.next());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/dfs/DFSWorklistItem.java b/src/main/java/com/android/tools/r8/utils/dfs/DFSWorklistItem.java
new file mode 100644
index 0000000..4020cea
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/dfs/DFSWorklistItem.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, 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.utils.dfs;
+
+public abstract class DFSWorklistItem<T> {
+
+ T value;
+
+ public DFSWorklistItem(T value) {
+ this.value = value;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public boolean isFullyVisited() {
+ return false;
+ }
+
+ public boolean isNewlyVisited() {
+ return false;
+ }
+
+ public NewlyVisitedDFSWorklistItem<T> asNewlyVisited() {
+ return null;
+ }
+
+ public static class NewlyVisitedDFSWorklistItem<T> extends DFSWorklistItem<T> {
+
+ public NewlyVisitedDFSWorklistItem(T value) {
+ super(value);
+ }
+
+ @Override
+ public boolean isNewlyVisited() {
+ return true;
+ }
+
+ @Override
+ public NewlyVisitedDFSWorklistItem<T> asNewlyVisited() {
+ return this;
+ }
+
+ public FullyVisitedDFSWorklistItem<T> toFullyVisited() {
+ return new FullyVisitedDFSWorklistItem<>(getValue());
+ }
+ }
+
+ public static class FullyVisitedDFSWorklistItem<T> extends DFSWorklistItem<T> {
+
+ public FullyVisitedDFSWorklistItem(T value) {
+ super(value);
+ }
+
+ @Override
+ public boolean isFullyVisited() {
+ return true;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index b3a595a..ce82006 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -469,17 +469,6 @@
options -> options.testing.allowClassInliningOfSynthetics = false);
}
- public T noClassStaticizing() {
- return noClassStaticizing(true);
- }
-
- public T noClassStaticizing(boolean condition) {
- if (condition) {
- return addOptionsModification(options -> options.enableClassStaticizer = false);
- }
- return self();
- }
-
public T noHorizontalClassMerging() {
return noHorizontalClassMerging(true);
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index be94525..ac6ca8e 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -34,6 +34,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -79,7 +80,7 @@
private PrintStream oldStderr = null;
protected OutputMode outputMode = OutputMode.DexIndexed;
- private boolean isAndroidBuildVersionAdded = false;
+ private Optional<Integer> isAndroidBuildVersionAdded = null;
LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration =
LibraryDesugaringTestConfiguration.DISABLED;
@@ -89,8 +90,13 @@
}
public T addAndroidBuildVersion() {
+ return addAndroidBuildVersion(null);
+ }
+
+ public T addAndroidBuildVersion(AndroidApiLevel specifiedApiLevel) {
addProgramClasses(AndroidBuildVersion.class);
- isAndroidBuildVersionAdded = true;
+ isAndroidBuildVersionAdded =
+ Optional.ofNullable(specifiedApiLevel == null ? null : specifiedApiLevel.getLevel());
return self();
}
@@ -257,8 +263,10 @@
cr =
internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build), benchmark)
.addRunClasspathFiles(additionalRunClassPath);
- if (isAndroidBuildVersionAdded) {
- cr.setSystemProperty(AndroidBuildVersion.PROPERTY, "" + builder.getMinApiLevel());
+ if (isAndroidBuildVersionAdded != null) {
+ cr.setSystemProperty(
+ AndroidBuildVersion.PROPERTY,
+ "" + isAndroidBuildVersionAdded.orElse(builder.getMinApiLevel()));
}
return cr;
} finally {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
new file mode 100644
index 0000000..42ee1c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2022, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static org.junit.Assume.assumeFalse;
+
+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.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/216136762.
+@RunWith(Parameterized.class)
+public class ApiModelOutlineMethodProtectedTest extends TestBase {
+
+ private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+ private static final AndroidApiLevel methodApiLevel = AndroidApiLevel.O_MR1;
+
+ private static final String TESTCLASS_DESCRIPTOR = "Lfoo/bar/Baz;";
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ // TODO(b/197078995): Make this work on 12.
+ assumeFalse(
+ parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V12_0_0));
+ Method[] apiMethods =
+ new Method[] {
+ LibraryClass.class.getDeclaredMethod("addedOn27"),
+ LibraryClass.class.getDeclaredMethod("alsoAddedOn27"),
+ LibraryClass.class.getDeclaredMethod("superInvokeOn27")
+ };
+ AndroidApiLevel runApiLevel =
+ parameters.isCfRuntime()
+ ? AndroidApiLevel.B
+ : parameters.getRuntime().maxSupportedApiLevel();
+ boolean willInvokeLibraryMethods =
+ parameters.isDexRuntime() && runApiLevel.isGreaterThanOrEqualTo(methodApiLevel);
+ testForR8(parameters.getBackend())
+ .addLibraryClasses(LibraryClass.class)
+ .addLibraryFiles(
+ parameters.isCfRuntime()
+ ? ToolHelper.getJava8RuntimeJar()
+ : ToolHelper.getFirstSupportedAndroidJar(runApiLevel))
+ .addProgramClassFileData(
+ transformer(TestClass.class).setClassDescriptor(TESTCLASS_DESCRIPTOR).transform(),
+ transformer(Main.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TestClass.class), TESTCLASS_DESCRIPTOR)
+ .transform())
+ .addKeepMainRule(Main.class)
+ .setMinApi(AndroidApiLevel.B)
+ .addAndroidBuildVersion(runApiLevel)
+ .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+ .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
+ .apply(
+ builder -> {
+ for (Method apiMethod : apiMethods) {
+ setMockApiLevelForMethod(apiMethod, methodApiLevel).accept(builder);
+ }
+ })
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .compile()
+ .applyIf(willInvokeLibraryMethods, b -> b.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(!willInvokeLibraryMethods, "Not calling API")
+ .assertSuccessWithOutputLinesIf(
+ willInvokeLibraryMethods,
+ "Could not access LibraryClass::addedOn27",
+ "LibraryClass::addedOn27",
+ "LibraryClass::addedOn27",
+ "LibraryCLass::alsoAddedOn27",
+ "TestClass::superInvokeOn27",
+ "LibraryCLass::superInvokeOn27");
+ }
+
+ // Only present from api level 23.
+ public static class LibraryClass {
+
+ protected static void addedOn27() {
+ System.out.println("LibraryClass::addedOn27");
+ }
+
+ protected void alsoAddedOn27() {
+ System.out.println("LibraryCLass::alsoAddedOn27");
+ }
+
+ protected void superInvokeOn27() {
+ System.out.println("LibraryCLass::superInvokeOn27");
+ }
+ }
+
+ @NeverClassInline
+ public static class /* foo.bar */ TestClass extends LibraryClass {
+
+ @NeverInline
+ public static void callAddedOn27() {
+ LibraryClass.addedOn27();
+ }
+
+ @NeverInline
+ public void test() {
+ addedOn27();
+ LibraryClass libraryClass = this;
+ libraryClass.alsoAddedOn27();
+ libraryClass.superInvokeOn27();
+ }
+
+ @Override
+ @NeverInline
+ protected void superInvokeOn27() {
+ System.out.println("TestClass::superInvokeOn27");
+ super.superInvokeOn27();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (AndroidBuildVersion.VERSION >= 27) {
+ try {
+ TestClass.addedOn27();
+ } catch (IllegalAccessError accessError) {
+ System.out.println("Could not access LibraryClass::addedOn27");
+ }
+ TestClass.callAddedOn27();
+ new TestClass().test();
+ } else {
+ System.out.println("Not calling API");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
index 3f887af..465cd5e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
@@ -40,7 +40,6 @@
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
.enableNeverClassInliningAnnotations()
- .noClassStaticizing()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("A", "B");
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java
index e6752f8..8e2433a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java
@@ -36,7 +36,6 @@
inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
- .noClassStaticizing()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
index fa19056..f941a29 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
@@ -7,9 +7,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -26,14 +23,4 @@
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
-
- protected ClassSubject getSynthesizedArgumentClassSubject(CodeInspector codeInspector) {
- return codeInspector.allClasses().stream()
- .filter(
- clazz ->
- SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument(
- clazz.getOriginalReference()))
- .findFirst()
- .get();
- }
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
index ef0cedf..1f413bc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
@@ -11,9 +11,6 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.A;
-import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B;
-import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main;
import org.junit.Test;
public class InheritInterfaceWithDefaultTest extends HorizontalClassMergingTestBase {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
index 17e3f64..eb3138c 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
@@ -45,8 +45,6 @@
testForR8(parameters.getBackend())
.addInnerClasses(PutObjectWithFinalizeTest.class)
.addKeepMainRule(TestClass.class)
- // The class staticizer does not consider the finalize() method.
- .addOptionsModification(options -> options.enableClassStaticizer = false)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 15f68fc..ad9ca05 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -170,14 +170,13 @@
assertEquals(
Lists.newArrayList(
"STATIC: String SimpleWithParams.bar(String)",
- "STATIC: String TrivialTestClass.next()",
- "SimpleWithParams SimpleWithParams.INSTANCE",
- "VIRTUAL: String SimpleWithParams.foo()"),
+ "STATIC: String SimpleWithParams.foo()",
+ "STATIC: String TrivialTestClass.next()"),
references(clazz, "testSimpleWithParams", "void"));
ClassSubject simpleWithParams = inspector.clazz(SimpleWithParams.class);
- assertFalse(instanceMethods(simpleWithParams).isEmpty());
- assertThat(simpleWithParams.clinit(), isPresent());
+ assertTrue(instanceMethods(simpleWithParams).isEmpty());
+ assertThat(simpleWithParams.clinit(), isAbsent());
assertEquals(
Lists.newArrayList(
@@ -198,21 +197,21 @@
"STATIC: String TrivialTestClass.next()"),
references(clazz, "testSimpleWithGetter", "void"));
+ // TODO(b/216254482): This can be optimized by pruning (always) simple caller inlined methods
+ // after the primary optimization pass.
ClassSubject simpleWithGetter = inspector.clazz(SimpleWithGetter.class);
- assertTrue(instanceMethods(simpleWithGetter).isEmpty());
- assertThat(simpleWithGetter.clinit(), not(isPresent()));
+ assertEquals(1, instanceMethods(simpleWithGetter).size());
+ assertThat(simpleWithGetter.clinit(), isPresent());
assertEquals(
Lists.newArrayList(
- "STATIC: String SimpleWithThrowingGetter.bar(String)",
- "STATIC: String TrivialTestClass.next()",
- "SimpleWithThrowingGetter SimpleWithThrowingGetter.INSTANCE",
- "VIRTUAL: String SimpleWithThrowingGetter.foo()"),
+ "STATIC: String SimpleWithLazyInit.bar$1(String)",
+ "STATIC: String SimpleWithLazyInit.foo$1()",
+ "STATIC: String TrivialTestClass.next()"),
references(clazz, "testSimpleWithThrowingGetter", "void"));
ClassSubject simpleWithThrowingGetter = inspector.clazz(SimpleWithThrowingGetter.class);
- assertFalse(instanceMethods(simpleWithThrowingGetter).isEmpty());
- assertThat(simpleWithThrowingGetter.clinit(), isPresent());
+ assertThat(simpleWithThrowingGetter, isAbsent());
// TODO(b/143389508): add support for lazy init in singleton instance getter.
assertEquals(
@@ -220,19 +219,19 @@
"DIRECT: void SimpleWithLazyInit.<init>()",
"DIRECT: void SimpleWithLazyInit.<init>()",
"STATIC: String SimpleWithLazyInit.bar(String)",
+ "STATIC: String SimpleWithLazyInit.foo()",
"STATIC: String TrivialTestClass.next()",
"SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
"SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
"SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
"SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
"SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
- "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
- "VIRTUAL: String SimpleWithLazyInit.foo()"),
+ "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE"),
references(clazz, "testSimpleWithLazyInit", "void"));
ClassSubject simpleWithLazyInit = inspector.clazz(SimpleWithLazyInit.class);
assertFalse(instanceMethods(simpleWithLazyInit).isEmpty());
- assertThat(simpleWithLazyInit.clinit(), not(isPresent()));
+ assertThat(simpleWithLazyInit.clinit(), isPresent());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
index 828f4c0..35b475b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
@@ -10,7 +10,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -22,6 +23,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
/**
@@ -35,15 +37,12 @@
@RunWith(Parameterized.class)
public class InvokeMethodWithNonNullParamCheckTest extends TestBase {
- private final Backend backend;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
- }
-
- public InvokeMethodWithNonNullParamCheckTest(Backend backend) {
- this.backend = backend;
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
@Test
@@ -72,7 +71,7 @@
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(InvokeMethodWithNonNullParamCheckTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
@@ -82,7 +81,8 @@
// CatchHandlers(new A()).
options.enableClassInlining = false;
})
- .run(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
.inspector();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/EffectivelyUnusedNullableArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/EffectivelyUnusedNullableArgumentTest.java
new file mode 100644
index 0000000..54d2071
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/EffectivelyUnusedNullableArgumentTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2022, 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.unusedarguments;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+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.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EffectivelyUnusedNullableArgumentTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ // TODO(b/173398086): uniqueMethodWithName() does not work with argument changes.
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+
+ MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithName("foo");
+ assertThat(fooMethodSubject, isPresent());
+ assertEquals(1, fooMethodSubject.getProgramMethod().getParameters().size());
+ assertEquals(
+ aClassSubject.getDexProgramClass().getType(),
+ fooMethodSubject.getProgramMethod().getParameter(0));
+ assertThat(
+ fooMethodSubject,
+ invokesMethodWithName(
+ canUseJavaUtilObjectsRequireNonNull(parameters)
+ ? "requireNonNull"
+ : "getClass"));
+
+ MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithName("bar");
+ assertThat(barMethodSubject, isStatic());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(NullPointerException.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A a = System.currentTimeMillis() > 0 ? null : new A();
+ A.foo(a);
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ // This parameter cannot be removed, since the program needs to fail at the call to A.bar() when
+ // A.foo(null) is called.
+ @NeverInline
+ static void foo(A a) {
+ a.bar();
+ }
+
+ @NeverInline
+ void bar() {
+ System.out.println("A.bar()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index 1b96692..f503ce8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -11,12 +11,12 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import com.google.common.base.Predicates;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,45 +44,18 @@
public void testCompanionAndRegularObjects() throws Exception {
assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
final String mainClassName = "class_staticizer.MainKt";
-
- // Without class staticizer.
- runTest("class_staticizer", mainClassName, true)
- .inspect(
- inspector -> {
- assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent());
-
- // The Util class is there, but its instance methods have been inlined.
- ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
- assertThat(utilClass, isPresent());
- assertTrue(
- utilClass.allMethods().stream()
- .filter(Predicates.not(FoundMethodSubject::isStatic))
- .allMatch(FoundMethodSubject::isInstanceInitializer));
- });
-
- // With class staticizer.
- runTest("class_staticizer", mainClassName, false)
+ runTest("class_staticizer", mainClassName)
.inspect(
inspector -> {
assertThat(inspector.clazz("class_staticizer.Regular$Companion"), not(isPresent()));
ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
assertThat(utilClass, isPresent());
- // TODO(b/179951488): The <init> is not removed in CF
- if (testParameters.isDexRuntime()) {
- assertTrue(utilClass.allMethods().stream().allMatch(FoundMethodSubject::isStatic));
- }
+ assertTrue(utilClass.allMethods().stream().allMatch(FoundMethodSubject::isStatic));
});
}
- protected R8TestRunResult runTest(String folder, String mainClass, boolean noClassStaticizing)
- throws Exception {
- return runTest(
- folder,
- mainClass,
- testBuilder ->
- testBuilder
- .noClassInlining()
- .noClassStaticizing(noClassStaticizing));
+ protected R8TestRunResult runTest(String folder, String mainClass) throws Exception {
+ return runTest(folder, mainClass, R8TestBuilder::noClassInlining);
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index 50e9c6e..c53700b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -73,22 +73,6 @@
}
@Test
- public void testDontShrinkAndDontOptimizeDifferently() throws Exception {
- test(
- builder ->
- builder
- .addKeepRules("-keep,allowobfuscation class **.*KClasses*")
- .noTreeShaking()
- .addOptionsModification(
- o -> {
- // Randomly choose a couple of optimizations.
- o.enableVerticalClassMerging = false;
- o.enableClassStaticizer = false;
- o.outline.enabled = false;
- }));
- }
-
- @Test
public void testDontShrinkAndDontObfuscate() throws Exception {
test(builder -> builder.noMinification().noTreeShaking());
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index b3af78a..907ca27 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -11,7 +11,6 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.KotlinTestParameters;
-import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -87,7 +86,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePrimitiveProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass)
.inspect(
inspector -> {
if (allowAccessModification) {
@@ -107,9 +106,9 @@
MemberNaming.MethodSignature setterAccessor =
testedClass.getSetterAccessorForProperty(
propertyName, AccessorKind.FROM_COMPANION);
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsRemoved(outerClass, setterAccessor);
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsKept(outerClass, getterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
});
}
@@ -118,7 +117,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePrivateProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass)
.inspect(
inspector -> {
if (allowAccessModification) {
@@ -139,10 +138,10 @@
MemberNaming.MethodSignature setterAccessor =
testedClass.getSetterAccessorForProperty(
propertyName, AccessorKind.FROM_COMPANION);
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsRemoved(outerClass, setterAccessor);
+ checkMethodIsKept(outerClass, getterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
});
}
@@ -151,7 +150,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_useInternalProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass)
.inspect(
inspector -> {
if (allowAccessModification) {
@@ -184,7 +183,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePublicProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass)
.inspect(
inspector -> {
if (allowAccessModification) {
@@ -206,9 +205,9 @@
testedClass.getSetterAccessorForProperty(
propertyName, AccessorKind.FROM_COMPANION);
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsRemoved(outerClass, setterAccessor);
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsKept(outerClass, getterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
});
}
@@ -217,7 +216,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass)
.inspect(
inspector -> {
if (allowAccessModification) {
@@ -270,10 +269,7 @@
runTest(
"accessors",
mainClass,
- builder -> {
- builder.addClasspathFiles(kotlinc.getKotlinAnnotationJar());
- builder.noClassStaticizing();
- })
+ builder -> builder.addClasspathFiles(kotlinc.getKotlinAnnotationJar()))
.inspect(
inspector -> {
// The classes are removed entirely as a result of member value propagation, inlining,
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index c979be9..59ba99b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -91,7 +91,6 @@
o -> {
o.enableClassInlining = false;
o.enableVerticalClassMerging = false;
- o.enableClassStaticizer = false;
};
@Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineBranchTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineBranchTest.java
new file mode 100644
index 0000000..92b6019
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineBranchTest.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2022, 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.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceInlineBranchTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ public StackTrace expectedStackTrace;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ // Get the expected stack trace by running on the JVM.
+ expectedStackTrace =
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .getStackTrace();
+ }
+
+ @Test
+ public void testR8() throws Throwable {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepAttributeLineNumberTable()
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .inspectStackTrace(
+ (stackTrace, inspector) -> {
+ if (canUseJavaUtilObjectsRequireNonNull(parameters)) {
+ StackTrace requireNonNullFrame =
+ StackTrace.builder().add(stackTrace.get(0)).build();
+ assertThat(
+ requireNonNullFrame,
+ isSameExceptForLineNumbers(
+ StackTrace.builder()
+ .add(
+ StackTraceLine.builder()
+ .setClassName(Objects.class.getTypeName())
+ .setMethodName("requireNonNull")
+ .setFileName("Objects.java")
+ .build())
+ .build()));
+
+ StackTrace stackTraceWithoutRequireNonNull =
+ StackTrace.builder().add(stackTrace).remove(0).build();
+ assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace));
+ } else {
+ // To check that we inserted the check in the branch correctly we use a rudimentary
+ // line check by comparing that the getClass method comes later than the first if.
+ MethodSubject methodSubject =
+ inspector.clazz(Main.class).uniqueMethodWithName("call");
+ assertThat(methodSubject, isPresent());
+ List<InstructionSubject> getClassCalls =
+ methodSubject
+ .streamInstructions()
+ .filter(
+ instructionSubject ->
+ instructionSubject.isInvoke()
+ && instructionSubject
+ .getMethod()
+ .qualifiedName()
+ .contains("getClass"))
+ .collect(Collectors.toList());
+ assertEquals(1, getClassCalls.size());
+ Optional<InstructionSubject> firstIf =
+ methodSubject.streamInstructions().filter(InstructionSubject::isIf).findFirst();
+ assertTrue(firstIf.isPresent());
+ assertTrue(
+ methodSubject.getLineNumberForInstruction(getClassCalls.get(0))
+ > methodSubject.getLineNumberForInstruction(firstIf.get()));
+ assertThat(stackTrace, isSame(expectedStackTrace));
+ }
+ });
+ }
+
+ @NeverClassInline
+ static class Foo {
+
+ private int value;
+
+ @NeverInline
+ int getValue() {
+ return value;
+ }
+
+ @NeverInline
+ void setValue(int value) {
+ this.value = value;
+ }
+
+ void inlinable(int arg) {
+ String newValue;
+ if (arg > 0) {
+ newValue = getValue() + "";
+ } else {
+ if (arg < 0) {
+ setValue(arg);
+ newValue = "-1";
+ } else {
+ // When inlining this block this is the only path that needs a null-check.
+ newValue = "Hello World";
+ }
+ }
+ System.out.println(newValue);
+ }
+ }
+
+ static class Main {
+
+ @NeverInline
+ private static void call(Foo foo, String[] args) {
+ foo.inlinable(args.length);
+ }
+
+ public static void main(String[] args) {
+ call(args.length == 0 ? null : new Foo(), args);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
index 15466f7..36921ce 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
@@ -4,13 +4,16 @@
package com.android.tools.r8.naming.retrace;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import org.hamcrest.CoreMatchers;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -22,25 +25,39 @@
@Parameter() public TestParameters parameters;
+ public StackTrace expectedStackTrace;
+
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
+ @Before
+ public void setup() throws Exception {
+ // Get the expected stack trace by running on the JVM.
+ TestBuilder<? extends SingleTestRunResult<?>, ?> testBuilder =
+ testForRuntime(parameters).addInnerClasses(getClass());
+ SingleTestRunResult<?> runResult = testBuilder.run(parameters.getRuntime(), Main.class);
+ if (parameters.isCfRuntime()) {
+ expectedStackTrace = runResult.map(StackTrace::extractFromJvm);
+ } else {
+ expectedStackTrace =
+ StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm());
+ }
+ }
+
@Test
public void testR8() throws Throwable {
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .compile()
- .inspectProguardMap(
- map -> {
- // TODO(b/215339687): We should not have a rewriteFrame in the mapping file since
- // an explicit null check should be inserted.
- assertThat(map, CoreMatchers.containsString("com.android.tools.r8.rewriteFrame"));
- });
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compile()
+ .inspectProguardMap(
+ map -> {
+ assertThat(
+ map, not(CoreMatchers.containsString("com.android.tools.r8.rewriteFrame")));
+ });
}
static class Foo {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
index b5cd101..15f867f 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
@@ -31,12 +31,10 @@
}
public StackTrace expectedStackTrace;
- public StackTrace unexpectedStackTrace;
@Before
public void setup() throws Exception {
expectedStackTrace = getStackTrace();
- unexpectedStackTrace = getStackTrace("foo");
}
private StackTrace getStackTrace(String... args) throws Exception {
@@ -62,9 +60,8 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Caller.class)
.assertFailureWithErrorThatThrows(NullPointerException.class)
- // TODO(b/214377135): Should retrace to expectedStackTrace.
.inspectStackTrace(
- (stackTrace, codeInspector) -> assertThat(stackTrace, isSame(unexpectedStackTrace)));
+ (stackTrace, codeInspector) -> assertThat(stackTrace, isSame(expectedStackTrace)));
}
static class Foo {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
index 6143522..6b7b2d0 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
@@ -4,19 +4,21 @@
package com.android.tools.r8.naming.retrace;
import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoMethodStaticizing;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
+import java.util.Objects;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,8 +73,28 @@
MethodSubject staticized = callerClass.uniqueMethodWithName("outerCaller");
assertThat(staticized, isPresentAndRenamed());
// TODO(b/214377135): The stack traces should be the same (when 206427323) is
- // resolved.
- assertThat(stackTrace, notIf(isSame(expectedStackTrace), throwReceiverNpe));
+ // resolved.
+ if (throwReceiverNpe && canUseJavaUtilObjectsRequireNonNull(parameters)) {
+ StackTrace requireNonNullFrame =
+ StackTrace.builder().add(stackTrace.get(0)).build();
+ assertThat(
+ requireNonNullFrame,
+ isSameExceptForLineNumbers(
+ StackTrace.builder()
+ .add(
+ StackTraceLine.builder()
+ .setClassName(Objects.class.getTypeName())
+ .setMethodName("requireNonNull")
+ .setFileName("Objects.java")
+ .build())
+ .build()));
+
+ StackTrace stackTraceWithoutRequireNonNull =
+ StackTrace.builder().add(stackTrace).remove(0).build();
+ assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace));
+ } else {
+ assertThat(stackTrace, isSame(expectedStackTrace));
+ }
});
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
index 13e3e7f..70c2be4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
@@ -8,11 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.BooleanUtils;
@@ -43,46 +39,36 @@
public StackTrace expectedStackTrace;
+ private String[] getArgs() {
+ return throwReceiverNpe ? new String[] {"foo"} : new String[0];
+ }
+
@Before
public void setup() throws Exception {
- // Get the expected stack trace by running on the JVM.
- TestBuilder<? extends SingleTestRunResult<?>, ?> testBuilder =
- testForRuntime(parameters).addProgramClasses(Caller.class, Foo.class);
- SingleTestRunResult<?> runResult;
- if (throwReceiverNpe) {
- runResult = testBuilder.run(parameters.getRuntime(), Caller.class, "foo");
- } else {
- runResult = testBuilder.run(parameters.getRuntime(), Caller.class);
- }
- if (parameters.isCfRuntime()) {
- expectedStackTrace = runResult.map(StackTrace::extractFromJvm);
- } else {
- expectedStackTrace =
- StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm());
- }
+ // Get the expected stack trace by running on the JVM/ART.
+ expectedStackTrace =
+ testForRuntime(parameters)
+ .addProgramClasses(Caller.class, Foo.class)
+ .run(parameters.getRuntime(), Caller.class, getArgs())
+ .getStackTrace();
}
@Test
public void testR8() throws Exception {
- R8FullTestBuilder r8FullTestBuilder =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepMainRule(Caller.class)
- .addKeepAttributeLineNumberTable()
- .addKeepAttributeSourceFile()
- .setMinApi(parameters.getApiLevel())
- .enableInliningAnnotations()
- .enableExperimentalMapFileVersion();
- R8TestRunResult runResult;
- if (throwReceiverNpe) {
- runResult = r8FullTestBuilder.run(parameters.getRuntime(), Caller.class, "foo");
- } else {
- runResult = r8FullTestBuilder.run(parameters.getRuntime(), Caller.class);
- }
- runResult
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Caller.class)
+ .addKeepAttributeLineNumberTable()
+ .addKeepAttributeSourceFile()
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableExperimentalMapFileVersion()
+ .run(parameters.getRuntime(), Caller.class, getArgs())
.assertFailureWithErrorThatThrows(NullPointerException.class)
.inspectStackTrace(
(stackTrace, codeInspector) -> {
+ // TODO(b/214377135): The stack traces should be the same (when 206427323) is
+ // resolved.
if (throwReceiverNpe && canUseJavaUtilObjectsRequireNonNull(parameters)) {
StackTrace requireNonNullFrame =
StackTrace.builder().add(stackTrace.get(0)).build();
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
index 2200ea7..10fec23 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
@@ -6,9 +6,11 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -51,6 +53,14 @@
public void runTest(List<String> keepRules, BiConsumer<StackTrace, StackTrace> checker)
throws Exception {
+ runTest(keepRules, checker, ThrowableConsumer.empty());
+ }
+
+ public void runTest(
+ List<String> keepRules,
+ BiConsumer<StackTrace, StackTrace> checker,
+ ThrowableConsumer<R8TestCompileResult> compileResultConsumer)
+ throws Exception {
R8TestRunResult result =
(compat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
.setMode(mode)
@@ -60,6 +70,8 @@
.addKeepRules(keepRules)
.setMinApi(parameters.getApiLevel())
.apply(this::configure)
+ .compile()
+ .apply(compileResultConsumer)
.run(parameters.getRuntime(), getMainClass())
.assertFailure();
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
index 5905930..bb37a8a 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -5,6 +5,8 @@
import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileName;
import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileNameAndLineNumber;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
@@ -12,11 +14,16 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -75,45 +82,54 @@
return height;
}
- private boolean filterSynthesizedMethod(StackTraceLine retracedStackTraceLine) {
- return retracedStackTraceLine.lineNumber > 0;
+ private boolean filterSynthesizedMethod(
+ StackTraceLine retracedStackTraceLine, MethodSubject syntheticMethod) {
+ if (syntheticMethod.isPresent()) {
+ String qualifiedMethodName =
+ retracedStackTraceLine.className + "." + retracedStackTraceLine.methodName;
+ return !qualifiedMethodName.equals(syntheticMethod.getOriginalName())
+ || retracedStackTraceLine.lineNumber > 0;
+ }
+ return true;
}
@Test
public void testSourceFileAndLineNumberTable() throws Exception {
+ Box<MethodSubject> syntheticMethod = new Box<>();
runTest(
ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
// Even when SourceFile is present retrace replaces the file name in the stack trace.
StackTrace reprocessedStackTrace =
- mode == CompilationMode.DEBUG
- ? retracedStackTrace
- : retracedStackTrace.filter(this::filterSynthesizedMethod);
+ retracedStackTrace.filter(
+ stackTraceLine -> filterSynthesizedMethod(stackTraceLine, syntheticMethod.get()));
assertThat(
reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
isSameExceptForFileName(
expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
- });
+ },
+ compileResult -> setSyntheticMethod(compileResult, syntheticMethod));
}
@Test
public void testLineNumberTableOnly() throws Exception {
assumeTrue(compat);
assumeTrue(parameters.isDexRuntime());
+ Box<MethodSubject> syntheticMethod = new Box<>();
runTest(
ImmutableList.of("-keepattributes LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
StackTrace reprocessedStackTrace =
- mode == CompilationMode.DEBUG
- ? retracedStackTrace
- : retracedStackTrace.filter(this::filterSynthesizedMethod);
+ retracedStackTrace.filter(
+ stackTraceLine -> filterSynthesizedMethod(stackTraceLine, syntheticMethod.get()));
assertThat(
reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
isSameExceptForFileName(
expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
- });
+ },
+ compileResult -> setSyntheticMethod(compileResult, syntheticMethod));
}
@Test
@@ -121,18 +137,32 @@
assumeTrue(compat);
assumeTrue(parameters.isDexRuntime());
haveSeenLines.clear();
+ Box<MethodSubject> syntheticMethod = new Box<>();
runTest(
ImmutableList.of(),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
StackTrace reprocessedStackTrace =
- mode == CompilationMode.DEBUG
- ? retracedStackTrace
- : retracedStackTrace.filter(this::filterSynthesizedMethod);
+ retracedStackTrace.filter(
+ stackTraceLine -> filterSynthesizedMethod(stackTraceLine, syntheticMethod.get()));
assertThat(
reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
isSameExceptForFileNameAndLineNumber(
expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ },
+ compileResult -> setSyntheticMethod(compileResult, syntheticMethod));
+ }
+
+ private void setSyntheticMethod(
+ R8TestCompileResult compileResult, Box<MethodSubject> syntheticMethod) throws IOException {
+ compileResult.inspect(
+ inspector -> {
+ ClassSubject tintResourcesClassSubject = inspector.clazz(TintResources.class);
+ MethodSubject uniqueSyntheticMethod =
+ tintResourcesClassSubject.uniqueMethodThatMatches(
+ method -> method.getAccessFlags().isSynthetic());
+ assertThat(uniqueSyntheticMethod, onlyIf(mode == CompilationMode.RELEASE, isPresent()));
+ syntheticMethod.set(uniqueSyntheticMethod);
});
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java
index f5fb4f5..828a4b5 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.optimize.argumentpropagation;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -16,6 +16,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
@@ -40,7 +41,6 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .addOptionsModification(options -> options.enableClassStaticizer = false)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -48,12 +48,19 @@
.compile()
.inspect(
inspector -> {
+ boolean isCf = parameters.isCfRuntime();
+
ClassSubject hostClassSubject = inspector.clazz(Host.class);
- assertThat(hostClassSubject, isAbsent());
+ assertThat(hostClassSubject, isPresent());
+ assertEquals(1 + BooleanUtils.intValue(isCf), hostClassSubject.allMethods().size());
+ assertThat(hostClassSubject.clinit(), onlyIf(isCf, isPresent()));
+ assertThat(hostClassSubject.uniqueMethodWithName("keepHost"), isPresent());
ClassSubject companionClassSubject = inspector.clazz(Host.Companion.class);
assertThat(companionClassSubject, isPresent());
- assertEquals(1, companionClassSubject.allMethods().size());
+ assertEquals(
+ 1 + BooleanUtils.intValue(isCf), companionClassSubject.allMethods().size());
+ assertThat(companionClassSubject.init(), onlyIf(isCf, isPresent()));
MethodSubject greetMethodSubject =
companionClassSubject.uniqueMethodWithName("greet");
@@ -67,6 +74,7 @@
public static void main(String[] args) {
Host.companion.greet();
+ Host.keepHost();
}
}
@@ -74,13 +82,18 @@
static final Companion companion = new Companion();
+ @NeverInline
+ static void keepHost() {
+ System.out.println();
+ }
+
@NeverClassInline
@NoHorizontalClassMerging
static class Companion {
@NeverInline
void greet() {
- System.out.println("Hello world!");
+ System.out.print("Hello world!");
}
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/EffectivelyFinalCompanionMethodsTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/EffectivelyFinalCompanionMethodsTest.java
new file mode 100644
index 0000000..67af262
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/EffectivelyFinalCompanionMethodsTest.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EffectivelyFinalCompanionMethodsTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject hostClassSubject = inspector.clazz(Host.class);
+ assertThat(hostClassSubject, isAbsent());
+
+ ClassSubject companionClassSubject = inspector.clazz(Host.Companion.class);
+ assertThat(companionClassSubject, isPresent());
+ assertEquals(4, companionClassSubject.allMethods().size());
+ assertThat(companionClassSubject.uniqueMethodWithName("foo"), isStatic());
+ assertThat(companionClassSubject.uniqueMethodWithName("bar"), isStatic());
+ assertThat(companionClassSubject.uniqueMethodWithName("baz"), isStatic());
+ assertThat(companionClassSubject.uniqueMethodWithName("qux"), isStatic());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo!", "Bar!", "Baz!", "Qux!", "Baz!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Host.companion.foo();
+ }
+ }
+
+ static class Host {
+
+ static final Companion companion = new Companion();
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class Companion {
+
+ @NeverInline
+ void foo() {
+ System.out.println("Foo!");
+ bar();
+ }
+
+ @NeverInline
+ void bar() {
+ System.out.println("Bar!");
+ baz(true);
+ }
+
+ @NeverInline
+ void baz(boolean doQux) {
+ System.out.println("Baz!");
+ if (doQux) {
+ qux();
+ }
+ }
+
+ @NeverInline
+ void qux() {
+ System.out.println("Qux!");
+ baz(false);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/NullableCompanionConstructorShakingTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/NullableCompanionConstructorShakingTest.java
new file mode 100644
index 0000000..594f86f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/NullableCompanionConstructorShakingTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NullableCompanionConstructorShakingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(options -> options.enableClassStaticizer = false)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject companionMethodSubject =
+ inspector.clazz(Host.Companion.class).uniqueMethodWithName("greet");
+ assertThat(companionMethodSubject, isStatic());
+
+ MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+ assertEquals(
+ 2,
+ mainMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvoke)
+ .count());
+ assertThat(
+ mainMethodSubject,
+ invokesMethodWithName(
+ canUseJavaUtilObjectsRequireNonNull(parameters)
+ ? "requireNonNull"
+ : "getClass"));
+ assertThat(mainMethodSubject, invokesMethod(companionMethodSubject));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(NullPointerException.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // This method call will be staticized and a null check should be inserted for Host.companion.
+ Host.companion.greet();
+ }
+ }
+
+ static class Host {
+
+ static final Companion companion = System.currentTimeMillis() >= 0 ? null : new Companion();
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class Companion {
+
+ @NeverInline
+ void greet() {
+ System.out.print("Hello world!");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b136698023/LibraryBridgeTest.java b/src/test/java/com/android/tools/r8/shaking/b136698023/LibraryBridgeTest.java
index 2ea5e50..baba02c 100644
--- a/src/test/java/com/android/tools/r8/shaking/b136698023/LibraryBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b136698023/LibraryBridgeTest.java
@@ -87,7 +87,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public LibraryBridgeTest(TestParameters parameters) {
@@ -102,7 +102,7 @@
.addKeepClassAndMembersRules(ICloneable.class)
.addKeepMainRule(MainWithOrdinaryClone.class)
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MainWithOrdinaryClone.class)
.assertSuccessWithOutputThatMatches(containsString(BaseOrdinaryClone.class.getTypeName()));
}
@@ -115,7 +115,7 @@
.addKeepClassAndMembersRules(XCloneable.class, Base.class)
.addKeepMainRule(Main.class)
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputThatMatches(containsString(Model.class.getTypeName()));
}
@@ -128,7 +128,7 @@
.addKeepClassAndMembersRules(XCloneable.class, Base.class)
.addKeepMainRule(MainImpl.class)
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MainImpl.class)
.assertSuccessWithOutputThatMatches(containsString(Model.class.getTypeName()));
}
@@ -140,15 +140,15 @@
testForR8(parameters.getBackend())
.addProgramClasses(Base.class)
.addKeepAllClassesRule()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile();
testForR8(parameters.getBackend())
.addProgramClasses(XCloneable.class, Model.class, Main.class)
.addLibraryClasses(Base.class)
- .addLibraryFiles(runtimeJar(parameters.getBackend()))
+ .addDefaultRuntimeLibrary(parameters)
.addKeepClassAndMembersRules(XCloneable.class)
.addKeepMainRule(Main.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.noMinification()
.compile()
.addRunClasspathFiles(library.writeToZip())
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index e7bb456..047a109 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -65,7 +65,6 @@
.addOptionsModification(
o -> {
o.enableDevirtualization = false;
- o.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false;
})
.enableAlwaysInliningAnnotations()
.noMinification()
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 6803e16..594b17a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -89,9 +89,9 @@
return Streams.stream(iterateTryCatches());
}
- public void getLineNumberForInstruction(InstructionSubject subject) {
+ public int getLineNumberForInstruction(InstructionSubject subject) {
assert hasLineNumberTable();
- getLineNumberTable().getLineForInstruction(subject);
+ return getLineNumberTable().getLineForInstruction(subject);
}
@Override