Revert "Remove old field optimizations"
Bug: b/339210038
Change-Id: If31d88e734769bfe52993558c47ffd0e702bac11
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index d87ade4..b9f074c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -4,14 +4,19 @@
package com.android.tools.r8.ir.analysis.fieldaccess;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -20,6 +25,7 @@
public class FieldAccessAnalysis {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final FieldAssignmentTracker fieldAssignmentTracker;
private final FieldBitAccessAnalysis fieldBitAccessAnalysis;
private final FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis;
private final FieldReadForWriteAnalysis fieldReadForWriteAnalysis;
@@ -29,6 +35,8 @@
this.appView = appView;
this.fieldBitAccessAnalysis =
options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null;
+ this.fieldAssignmentTracker =
+ options.enableFieldAssignmentTracker ? new FieldAssignmentTracker(appView) : null;
this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView);
this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView);
}
@@ -36,15 +44,28 @@
@VisibleForTesting
public FieldAccessAnalysis(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ FieldAssignmentTracker fieldAssignmentTracker,
FieldBitAccessAnalysis fieldBitAccessAnalysis,
FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis,
FieldReadForWriteAnalysis fieldReadForWriteAnalysis) {
this.appView = appView;
+ this.fieldAssignmentTracker = fieldAssignmentTracker;
this.fieldBitAccessAnalysis = fieldBitAccessAnalysis;
this.fieldReadForInvokeReceiverAnalysis = fieldReadForInvokeReceiverAnalysis;
this.fieldReadForWriteAnalysis = fieldReadForWriteAnalysis;
}
+ public FieldAssignmentTracker fieldAssignmentTracker() {
+ return fieldAssignmentTracker;
+ }
+
+ public void acceptClassInitializerDefaultsResult(
+ ClassInitializerDefaultsResult classInitializerDefaultsResult) {
+ if (fieldAssignmentTracker != null) {
+ fieldAssignmentTracker.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
+ }
+ }
+
public void recordFieldAccesses(
IRCode code,
BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
@@ -64,6 +85,9 @@
ProgramField field =
appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField();
if (field != null) {
+ if (fieldAssignmentTracker != null) {
+ fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field);
+ }
if (fieldBitAccessAnalysis != null) {
fieldBitAccessAnalysis.recordFieldAccess(
fieldInstruction, field.getDefinition(), feedback);
@@ -77,6 +101,14 @@
fieldInstruction, field, bytecodeMetadataProviderBuilder);
}
}
+ } else if (instruction.isNewInstance()) {
+ NewInstance newInstance = instruction.asNewInstance();
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
+ if (clazz != null) {
+ if (fieldAssignmentTracker != null) {
+ fieldAssignmentTracker.recordAllocationSite(newInstance, clazz, code.context());
+ }
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
new file mode 100644
index 0000000..af58cdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -0,0 +1,531 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.fieldaccess;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.BottomValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.ProgramFieldMap;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+// TODO(b/330674939): Remove legacy field optimizations.
+public class FieldAssignmentTracker {
+
+ private final AbstractValueFactory abstractValueFactory;
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
+
+ // A field access graph with edges from methods to the fields that they access. Edges are removed
+ // from the graph as we process methods, such that we can conclude that all field writes have been
+ // processed when a field no longer has any incoming edges.
+ private final FieldAccessGraph fieldAccessGraph;
+
+ // An object allocation graph with edges from methods to the classes they instantiate. Edges are
+ // removed from the graph as we process methods, such that we can conclude that all allocation
+ // sites have been seen when a class no longer has any incoming edges.
+ private final ObjectAllocationGraph objectAllocationGraph;
+
+ // Information about the fields in the program. If a field is not a key in the map then no writes
+ // has been seen to the field.
+ private final Map<DexEncodedField, ValueState> fieldStates = new ConcurrentHashMap<>();
+
+ private final Map<DexProgramClass, ProgramFieldMap<AbstractValue>>
+ abstractFinalInstanceFieldValues = new ConcurrentHashMap<>();
+
+ private final Set<DexProgramClass> classesWithPrunedInstanceInitializers =
+ ConcurrentHashMap.newKeySet();
+
+ FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
+ this.abstractValueFactory = appView.abstractValueFactory();
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.fieldAccessGraph = new FieldAccessGraph();
+ this.objectAllocationGraph = new ObjectAllocationGraph();
+ }
+
+ public void initialize() {
+ fieldAccessGraph.initialize(appView);
+ objectAllocationGraph.initialize(appView);
+ initializeAbstractInstanceFieldValues();
+ }
+
+ /**
+ * For each class with known allocation sites, adds a mapping from clazz -> instance field ->
+ * bottom.
+ *
+ * <p>If an entry (clazz, instance field) is missing in {@link #abstractFinalInstanceFieldValues},
+ * it is interpreted as if we known nothing about the value of the field.
+ */
+ private void initializeAbstractInstanceFieldValues() {
+ FieldAccessInfoCollection<?> fieldAccessInfos =
+ appView.appInfo().getFieldAccessInfoCollection();
+ ObjectAllocationInfoCollection objectAllocationInfos =
+ appView.appInfo().getObjectAllocationInfoCollection();
+ objectAllocationInfos.forEachClassWithKnownAllocationSites(
+ (clazz, allocationSites) -> {
+ if (appView.appInfo().isInstantiatedIndirectly(clazz)) {
+ // TODO(b/147652121): Handle classes that are instantiated indirectly.
+ return;
+ }
+ List<DexEncodedField> instanceFields = clazz.instanceFields();
+ if (instanceFields.isEmpty()) {
+ // No instance fields to track.
+ return;
+ }
+ ProgramFieldMap<AbstractValue> abstractFinalInstanceFieldValuesForClass =
+ ProgramFieldMap.create();
+ clazz.forEachProgramInstanceField(
+ field -> {
+ if (field.isFinalOrEffectivelyFinal(appView)) {
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.getReference());
+ if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
+ abstractFinalInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
+ }
+ }
+ });
+ if (!abstractFinalInstanceFieldValuesForClass.isEmpty()) {
+ abstractFinalInstanceFieldValues.put(clazz, abstractFinalInstanceFieldValuesForClass);
+ }
+ });
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.forEachProgramField(
+ field -> {
+ FieldAccessInfo accessInfo = fieldAccessInfos.get(field.getReference());
+ if (!appView.getKeepInfo(field).isValuePropagationAllowed(appView, field)
+ || (accessInfo != null && accessInfo.isWrittenFromMethodHandle())) {
+ fieldStates.put(field.getDefinition(), ValueState.unknown());
+ }
+ });
+ }
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ void acceptClassInitializerDefaultsResult(
+ ClassInitializerDefaultsResult classInitializerDefaultsResult) {
+ classInitializerDefaultsResult.forEachOptimizedField(
+ (field, value) -> {
+ DexType fieldType = field.getType();
+ if (value.isDefault(field.getType())) {
+ return;
+ }
+ assert fieldType.isClassType() || fieldType.isPrimitiveType();
+ fieldStates.compute(
+ field,
+ (f, fieldState) -> {
+ if (fieldState == null) {
+ AbstractValue abstractValue = value.toAbstractValue(abstractValueFactory);
+ if (fieldType.isClassType()) {
+ assert abstractValue.isSingleStringValue()
+ || abstractValue.isSingleDexItemBasedStringValue();
+ if (fieldType == dexItemFactory.stringType) {
+ return ConcreteClassTypeValueState.create(
+ abstractValue, DynamicType.definitelyNotNull());
+ } else {
+ ClassTypeElement nonNullableStringType =
+ dexItemFactory
+ .stringType
+ .toTypeElement(appView, definitelyNotNull())
+ .asClassType();
+ return ConcreteClassTypeValueState.create(
+ abstractValue, DynamicType.createExact(nonNullableStringType));
+ }
+ } else {
+ assert fieldType.isPrimitiveType();
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
+ }
+ }
+ // If the field is already assigned outside the class initializer then just give up.
+ return ValueState.unknown();
+ });
+ });
+ }
+
+ void recordFieldAccess(FieldInstruction instruction, ProgramField field) {
+ if (instruction.isFieldPut()) {
+ recordFieldPut(field, instruction.value());
+ }
+ }
+
+ private void recordFieldPut(ProgramField field, Value value) {
+ // For now only attempt to prove that fields are definitely null. In order to prove a single
+ // value for fields that are not definitely null, we need to prove that the given field is never
+ // read before it is written.
+ AbstractValue abstractValue =
+ value.isZero()
+ ? abstractValueFactory.createDefaultValue(field.getType())
+ : AbstractValue.unknown();
+ Nullability nullability = value.getType().nullability();
+ fieldStates.compute(
+ field.getDefinition(),
+ (f, fieldState) -> {
+ if (fieldState == null || fieldState.isBottom()) {
+ DexType fieldType = field.getType();
+ if (fieldType.isArrayType()) {
+ return ConcreteArrayTypeValueState.create(nullability);
+ }
+ if (fieldType.isPrimitiveType()) {
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
+ }
+ assert fieldType.isClassType();
+ DynamicType dynamicType =
+ WideningUtils.widenDynamicNonReceiverType(
+ appView,
+ value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
+ field.getType());
+ return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
+ }
+
+ if (fieldState.isUnknown()) {
+ return fieldState;
+ }
+
+ assert fieldState.isConcrete();
+
+ if (fieldState.isArrayState()) {
+ ConcreteArrayTypeValueState arrayFieldState = fieldState.asArrayState();
+ return arrayFieldState.mutableJoin(appView, field, nullability);
+ }
+
+ if (fieldState.isPrimitiveState()) {
+ ConcretePrimitiveTypeValueState primitiveFieldState = fieldState.asPrimitiveState();
+ return primitiveFieldState.mutableJoin(appView, field, abstractValue);
+ }
+
+ assert fieldState.isClassState();
+
+ ConcreteClassTypeValueState classFieldState = fieldState.asClassState();
+ DexType inStaticType = null;
+ return classFieldState.mutableJoin(
+ appView, abstractValue, value.getDynamicType(appView), inStaticType, field);
+ });
+ }
+
+ void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
+ ProgramFieldMap<AbstractValue> abstractInstanceFieldValuesForClass =
+ abstractFinalInstanceFieldValues.get(clazz);
+ if (abstractInstanceFieldValuesForClass == null) {
+ // We are not tracking the value of any of clazz' instance fields.
+ return;
+ }
+
+ InvokeDirect invoke = instruction.getUniqueConstructorInvoke(dexItemFactory);
+ if (invoke == null) {
+ // We just lost track.
+ abstractFinalInstanceFieldValues.remove(clazz);
+ return;
+ }
+
+ DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+ if (singleTarget == null) {
+ // We just lost track.
+ abstractFinalInstanceFieldValues.remove(clazz);
+ return;
+ }
+
+ InstanceFieldInitializationInfoCollection initializationInfoCollection =
+ singleTarget
+ .getDefinition()
+ .getOptimizationInfo()
+ .getInstanceInitializerInfo(invoke)
+ .fieldInitializationInfos();
+
+ // Synchronize on the lattice element (abstractInstanceFieldValuesForClass) in case we process
+ // another allocation site of `clazz` concurrently.
+ synchronized (abstractInstanceFieldValuesForClass) {
+ abstractInstanceFieldValuesForClass.removeIf(
+ (field, abstractValue, entry) -> {
+ InstanceFieldInitializationInfo initializationInfo =
+ initializationInfoCollection.get(field);
+ if (initializationInfo.isArgumentInitializationInfo()) {
+ InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
+ initializationInfo.asArgumentInitializationInfo();
+ Value argument =
+ invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
+ AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context);
+ abstractValue =
+ appView
+ .getAbstractValueFieldJoiner()
+ .join(abstractValue, argumentAbstractValue, field);
+ } else if (initializationInfo.isSingleValue()) {
+ SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
+ abstractValue =
+ appView
+ .getAbstractValueFieldJoiner()
+ .join(abstractValue, singleValueInitializationInfo, field);
+ } else if (initializationInfo.isTypeInitializationInfo()) {
+ // TODO(b/149732532): Not handled, for now.
+ abstractValue = UnknownValue.getInstance();
+ } else {
+ assert initializationInfo.isUnknown();
+ abstractValue = UnknownValue.getInstance();
+ }
+
+ assert !abstractValue.isBottom();
+ entry.setValue(abstractValue);
+ return abstractValue.isUnknown();
+ });
+ }
+ }
+
+ private void recordAllFieldPutsProcessed(
+ ProgramField field, OptimizationFeedbackDelayed feedback) {
+ ValueState fieldState =
+ fieldStates.getOrDefault(field.getDefinition(), ValueState.bottom(field));
+ AbstractValue abstractValue =
+ fieldState.isBottom()
+ ? appView.abstractValueFactory().createDefaultValue(field.getType())
+ : fieldState.getAbstractValue(appView);
+ if (abstractValue.isNonTrivial()) {
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ }
+
+ if (fieldState.isClassState() && field.getOptimizationInfo().getDynamicType().isUnknown()) {
+ ConcreteClassTypeValueState classFieldState = fieldState.asClassState();
+ DynamicType dynamicType = classFieldState.getDynamicType();
+ if (!dynamicType.isUnknown()) {
+ assert WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, field.getType())
+ .equals(dynamicType);
+ if (dynamicType.isNotNullType()) {
+ feedback.markFieldHasDynamicType(field, dynamicType);
+ } else {
+ DynamicTypeWithUpperBound staticType = field.getType().toDynamicType(appView);
+ if (dynamicType.asDynamicTypeWithUpperBound().strictlyLessThan(staticType, appView)) {
+ feedback.markFieldHasDynamicType(field, dynamicType);
+ }
+ }
+ }
+ }
+
+ if (!field.getAccessFlags().isStatic()) {
+ recordAllInstanceFieldPutsProcessed(field, feedback);
+ }
+ }
+
+ private void recordAllInstanceFieldPutsProcessed(
+ ProgramField field, OptimizationFeedbackDelayed feedback) {
+ if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+ return;
+ }
+ DexProgramClass clazz = field.getHolder();
+ if (classesWithPrunedInstanceInitializers.contains(clazz)) {
+ // The current method is analyzing the instance-puts to the field in the instance initializers
+ // of the holder class. Due to single caller inlining of instance initializers some of the
+ // methods needed for the analysis may have been pruned from the app, in which case the
+ // analysis result will not be conservative.
+ return;
+ }
+ AbstractValue abstractValue = BottomValue.getInstance();
+ for (DexEncodedMethod method : clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
+ InstanceFieldInitializationInfo fieldInitializationInfo =
+ method
+ .getOptimizationInfo()
+ .getContextInsensitiveInstanceInitializerInfo()
+ .fieldInitializationInfos()
+ .get(field);
+ if (fieldInitializationInfo.isSingleValue()) {
+ abstractValue =
+ appView
+ .getAbstractValueFieldJoiner()
+ .join(abstractValue, fieldInitializationInfo.asSingleValue(), field);
+ if (abstractValue.isUnknown()) {
+ break;
+ }
+ } else if (fieldInitializationInfo.isTypeInitializationInfo()) {
+ // TODO(b/149732532): Not handled, for now.
+ abstractValue = UnknownValue.getInstance();
+ break;
+ } else {
+ assert fieldInitializationInfo.isArgumentInitializationInfo()
+ || fieldInitializationInfo.isUnknown();
+ abstractValue = UnknownValue.getInstance();
+ break;
+ }
+ }
+
+ assert !abstractValue.isBottom();
+
+ if (!abstractValue.isUnknown()) {
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ }
+ }
+
+ private void recordAllAllocationsSitesProcessed(
+ DexProgramClass clazz, OptimizationFeedbackDelayed feedback) {
+ ProgramFieldMap<AbstractValue> abstractInstanceFieldValuesForClass =
+ abstractFinalInstanceFieldValues.get(clazz);
+ if (abstractInstanceFieldValuesForClass == null) {
+ return;
+ }
+
+ clazz.traverseProgramInstanceFields(
+ field -> {
+ AbstractValue abstractValue =
+ abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance());
+ if (abstractValue.isBottom()) {
+ feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz));
+ return TraversalContinuation.doBreak();
+ }
+ if (abstractValue.isNonTrivial()) {
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ }
+ return TraversalContinuation.doContinue();
+ });
+ }
+
+ public void onMethodPruned(ProgramMethod method) {
+ onMethodCodePruned(method);
+ }
+
+ public void onMethodCodePruned(ProgramMethod method) {
+ if (method.getDefinition().isInstanceInitializer()) {
+ classesWithPrunedInstanceInitializers.add(method.getHolder());
+ }
+ }
+
+ public void waveDone(ProgramMethodSet wave, OptimizationFeedbackDelayed feedback) {
+ // This relies on the instance initializer info in the method optimization feedback. It is
+ // therefore important that the optimization info has been flushed in advance.
+ assert feedback.noUpdatesLeft();
+ for (ProgramMethod method : wave) {
+ fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
+ objectAllocationGraph.markProcessed(
+ method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
+ }
+ feedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
+ feedback.updateVisibleOptimizationInfo();
+ }
+
+ static class FieldAccessGraph {
+
+ // The fields written by each method.
+ private final Map<DexEncodedMethod, List<ProgramField>> fieldWrites = new IdentityHashMap<>();
+
+ // The number of writes that have not yet been processed per field.
+ private final Reference2IntMap<DexEncodedField> pendingFieldWrites =
+ new Reference2IntOpenHashMap<>();
+
+ FieldAccessGraph() {}
+
+ public void initialize(AppView<AppInfoWithLiveness> appView) {
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+ appView.appInfo().getFieldAccessInfoCollection();
+ fieldAccessInfoCollection.forEach(
+ info -> {
+ ProgramField field =
+ appView.appInfo().resolveField(info.getField()).getSingleProgramField();
+ if (field == null) {
+ return;
+ }
+ if (!info.hasReflectiveAccess() && !info.isWrittenFromMethodHandle()) {
+ info.forEachWriteContext(
+ context ->
+ fieldWrites
+ .computeIfAbsent(context.getDefinition(), ignore -> new ArrayList<>())
+ .add(field));
+ pendingFieldWrites.put(field.getDefinition(), info.getNumberOfWriteContexts());
+ }
+ });
+ }
+
+ void markProcessed(ProgramMethod method, Consumer<ProgramField> allWritesSeenConsumer) {
+ List<ProgramField> fieldWritesInMethod = fieldWrites.get(method.getDefinition());
+ if (fieldWritesInMethod != null) {
+ for (ProgramField field : fieldWritesInMethod) {
+ int numberOfPendingFieldWrites = pendingFieldWrites.removeInt(field.getDefinition()) - 1;
+ if (numberOfPendingFieldWrites > 0) {
+ pendingFieldWrites.put(field.getDefinition(), numberOfPendingFieldWrites);
+ } else {
+ allWritesSeenConsumer.accept(field);
+ }
+ }
+ }
+ }
+ }
+
+ static class ObjectAllocationGraph {
+
+ // The classes instantiated by each method.
+ private final Map<DexEncodedMethod, List<DexProgramClass>> objectAllocations =
+ new IdentityHashMap<>();
+
+ // The number of allocation sites that have not yet been processed per class.
+ private final Reference2IntMap<DexProgramClass> pendingObjectAllocations =
+ new Reference2IntOpenHashMap<>();
+
+ ObjectAllocationGraph() {}
+
+ public void initialize(AppView<AppInfoWithLiveness> appView) {
+ ObjectAllocationInfoCollection objectAllocationInfos =
+ appView.appInfo().getObjectAllocationInfoCollection();
+ objectAllocationInfos.forEachClassWithKnownAllocationSites(
+ (clazz, contexts) -> {
+ for (DexEncodedMethod context : contexts) {
+ objectAllocations.computeIfAbsent(context, ignore -> new ArrayList<>()).add(clazz);
+ }
+ pendingObjectAllocations.put(clazz, contexts.size());
+ });
+ }
+
+ void markProcessed(
+ ProgramMethod method, Consumer<DexProgramClass> allAllocationsSitesSeenConsumer) {
+ List<DexProgramClass> allocationSitesInMethod = objectAllocations.get(method.getDefinition());
+ if (allocationSitesInMethod != null) {
+ for (DexProgramClass type : allocationSitesInMethod) {
+ int numberOfPendingAllocationSites = pendingObjectAllocations.removeInt(type) - 1;
+ if (numberOfPendingAllocationSites > 0) {
+ pendingObjectAllocations.put(type, numberOfPendingAllocationSites);
+ } else {
+ allAllocationsSitesSeenConsumer.accept(type);
+ }
+ }
+ }
+ }
+ }
+}
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 b18d828..23bebb5 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
@@ -135,7 +135,7 @@
private void computeFieldsWithNonTrivialValue() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
- for (DexEncodedField field : clazz.fields()) {
+ for (DexEncodedField field : clazz.instanceFields()) {
FieldClassification fieldClassification = classifyField(field, appView);
switch (fieldClassification) {
case CONSTANT:
@@ -143,7 +143,7 @@
constantFields.add(field);
break;
case NON_CONSTANT:
- // Reprocess reads to allow branch pruning and value propagation.
+ // Only reprocess writes, to allow branch pruning.
nonConstantFields.add(field);
break;
default:
@@ -151,6 +151,17 @@
break;
}
}
+ if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) {
+ for (DexEncodedField field : clazz.staticFields()) {
+ FieldClassification fieldClassification = classifyField(field, appView);
+ if (fieldClassification == FieldClassification.CONSTANT) {
+ constantFields.add(field);
+ } else {
+ assert fieldClassification == FieldClassification.NON_CONSTANT
+ || fieldClassification == FieldClassification.UNKNOWN;
+ }
+ }
+ }
}
assert verifyNoConstantFieldsOnSynthesizedClasses(appView);
}
@@ -192,6 +203,7 @@
});
}
+ @SuppressWarnings("ReferenceEquality")
private static FieldClassification classifyField(
DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
FieldAccessInfo fieldAccessInfo =
@@ -214,13 +226,10 @@
if (singleValue.isSingleFieldValue()) {
SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
DexField singleField = singleFieldValue.getField();
- if (singleField.isNotIdenticalTo(field.getReference())
+ if (singleField != field.getReference()
&& !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView)) {
return FieldClassification.CONSTANT;
}
- if (singleFieldValue.hasObjectState()) {
- return FieldClassification.NON_CONSTANT;
- }
}
return FieldClassification.UNKNOWN;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
index 5f0acbe..971a56e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
@@ -206,11 +206,6 @@
@Override
public abstract int hashCode();
- public DynamicType uncanonicalizeNotNullType(
- AppView<AppInfoWithLiveness> appView, DexType staticType) {
- return this;
- }
-
private static boolean verifyNotEffectivelyFinalClassType(
AppView<AppInfoWithLiveness> appView, TypeElement type) {
if (type.isClassType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
index d5f7fe1..7645341 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
@@ -75,10 +75,4 @@
public String toString() {
return "NotNullDynamicType";
}
-
- @Override
- public DynamicType uncanonicalizeNotNullType(
- AppView<AppInfoWithLiveness> appView, DexType staticType) {
- return DynamicType.create(appView, staticType.toNonNullTypeElement(appView));
- }
}
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 e87d0a8..7eb18f1 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
@@ -900,6 +900,9 @@
timing.begin("Analyze field accesses");
fieldAccessAnalysis.recordFieldAccesses(
code, bytecodeMetadataProviderBuilder, feedback, methodProcessor);
+ if (classInitializerDefaultsResult != null) {
+ fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
+ }
timing.end();
}
@@ -1090,6 +1093,7 @@
assert method.getHolder().lookupMethod(method.getReference()) == null;
appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.onMethodPruned(method));
enumUnboxer.onMethodPruned(method);
+ fieldAccessAnalysis.fieldAssignmentTracker().onMethodPruned(method);
numberUnboxer.onMethodPruned(method);
outliner.onMethodPruned(method);
if (inliner != null) {
@@ -1107,6 +1111,7 @@
appView.withArgumentPropagator(
argumentPropagator -> argumentPropagator.onMethodCodePruned(method));
enumUnboxer.onMethodCodePruned(method);
+ fieldAccessAnalysis.fieldAssignmentTracker().onMethodCodePruned(method);
numberUnboxer.onMethodCodePruned(method);
outliner.onMethodCodePruned(method);
if (inliner != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 9215753..035690f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -73,6 +73,10 @@
numberUnboxer.prepareForPrimaryOptimizationPass(timing, executorService);
outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
+ if (fieldAccessAnalysis != null && fieldAccessAnalysis.fieldAssignmentTracker() != null) {
+ fieldAccessAnalysis.fieldAssignmentTracker().initialize();
+ }
+
// Process the application identifying outlining candidates.
OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
PostMethodProcessor.Builder postMethodProcessorBuilder =
@@ -251,6 +255,9 @@
public void waveDone(ProgramMethodSet wave, ExecutorService executorService) {
delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
delayedOptimizationFeedback.updateVisibleOptimizationInfo();
+ if (fieldAccessAnalysis.fieldAssignmentTracker() != null) {
+ fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
+ }
appView.withArgumentPropagator(ArgumentPropagator::publishDelayedReprocessingCriteria);
if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 23517ba..23ae714 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.optimize;
import static com.android.tools.r8.graph.ProgramField.asProgramFieldOrNull;
-import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static com.android.tools.r8.utils.PredicateUtils.not;
@@ -687,9 +686,6 @@
if (replacement != null) {
if (isRedundantFieldLoadEliminationAllowed(field)) {
replacement.eliminateRedundantRead(it, instanceGet);
- if (field.isProgramField()) {
- getSimpleFeedback().markFieldAsPropagated(field.getDefinition());
- }
}
return;
}
@@ -778,9 +774,6 @@
FieldValue replacement = activeState.getStaticFieldValue(field.getReference());
if (replacement != null) {
replacement.eliminateRedundantRead(instructionIterator, staticGet);
- if (field.isProgramField()) {
- getSimpleFeedback().markFieldAsPropagated(field.getDefinition());
- }
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 7661efc..65487d8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -967,14 +967,14 @@
unboxedValues.put(field.getReference(), ordinalToUnboxedInt(ordinal));
ordinalToObjectState.put(ordinal, enumState);
if (isEnumWithSubtypes) {
- // If the dynamic type is a NotNull dynamic type, then uncanonicalize the dynamic
+ DynamicType dynamicType = field.getOptimizationInfo().getDynamicType();
+ // If the dynamic type is a NotNull dynamic type, then de-canonicalize the dynamic
// type. If the static type is an effectively final class then this yields an
// exact dynamic type.
- DynamicType dynamicType =
- field
- .getOptimizationInfo()
- .getDynamicType()
- .uncanonicalizeNotNullType(appView, field.getType());
+ if (dynamicType.isNotNullType()) {
+ dynamicType =
+ DynamicType.create(appView, field.getType().toNonNullTypeElement(appView));
+ }
if (dynamicType.isExactClassType()) {
valueTypes.put(ordinal, dynamicType.getExactClassType().getClassType());
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 0284378..9f3a782 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -151,11 +151,7 @@
// Since the value is a single field value, the type should be exact.
assert abstractValue.isSingleFieldValue();
- ClassTypeElement enumFieldType =
- optimizationInfo
- .getDynamicType()
- .uncanonicalizeNotNullType(appView.withLiveness(), field.getType())
- .getExactClassType();
+ ClassTypeElement enumFieldType = optimizationInfo.getDynamicType().getExactClassType();
if (enumFieldType == null) {
assert false : "Expected to have an exact dynamic type for enum instance";
continue;
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 dd7c96d..ff09253 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -409,6 +409,7 @@
// Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
public boolean enableFieldBitAccessAnalysis =
System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
+ public boolean enableFieldAssignmentTracker = true;
public boolean enableFieldValueAnalysis = true;
public boolean enableUnusedInterfaceRemoval = true;
public boolean enableDevirtualization = true;
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index f881944..834fa3c 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -50,6 +50,14 @@
}
}
+ static String allGeneratedMessageLiteSubtypesAreInstantiatedRule() {
+ return StringUtils.lines(
+ "-if class * extends com.google.protobuf.GeneratedMessageLite",
+ "-keep,allowobfuscation class <1> {",
+ " <init>(...);",
+ "}");
+ }
+
static String findLiteExtensionByNumberInDuplicateCalledRule() {
return StringUtils.lines(
"-keep class com.google.protobuf.proto2_registryGeneratedExtensionRegistryLiteDuplicate {",
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 1c8a244..ea825ed 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -87,7 +87,7 @@
OptimizationFeedbackMock feedback = new OptimizationFeedbackMock();
FieldBitAccessAnalysis fieldBitAccessAnalysis = new FieldBitAccessAnalysis();
FieldAccessAnalysis fieldAccessAnalysis =
- new FieldAccessAnalysis(appView, fieldBitAccessAnalysis, null, null);
+ new FieldAccessAnalysis(appView, null, fieldBitAccessAnalysis, null, null);
DexProgramClass clazz = appView.appInfo().classes().iterator().next();
assertEquals(TestClass.class.getTypeName(), clazz.type.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
index 16060c2..a603d83 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
@@ -4,13 +4,14 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverReprocessMethod;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -23,47 +24,49 @@
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 FieldWriteBeforeFieldReadTest extends TestBase {
- @Parameter(0)
- public TestParameters parameters;
+ private final TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
+ public FieldWriteBeforeFieldReadTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(FieldWriteBeforeFieldReadTest.class)
.addKeepMainRule(TestClass.class)
.addOptionsModification(
- options ->
- options.testing.waveModifier =
- (waves) -> {
- Function<String, Predicate<ProgramMethodSet>> wavePredicate =
- methodName ->
- wave ->
- wave.stream()
- .anyMatch(
- method -> method.toSourceString().contains(methodName));
- int readFieldsWaveIndex =
- IterableUtils.firstIndexMatching(
- waves, wavePredicate.apply("readFields"));
- assertTrue(readFieldsWaveIndex >= 0);
- int writeFieldsWaveIndex =
- IterableUtils.firstIndexMatching(
- waves, wavePredicate.apply("writeFields"));
- assertTrue(writeFieldsWaveIndex >= 0);
- assertTrue(writeFieldsWaveIndex < readFieldsWaveIndex);
- })
+ options -> {
+ options.testing.waveModifier =
+ (waves) -> {
+ Function<String, Predicate<ProgramMethodSet>> wavePredicate =
+ methodName ->
+ wave ->
+ wave.stream()
+ .anyMatch(
+ method -> method.toSourceString().contains(methodName));
+ int readFieldsWaveIndex =
+ IterableUtils.firstIndexMatching(waves, wavePredicate.apply("readFields"));
+ assertTrue(readFieldsWaveIndex >= 0);
+ int writeFieldsWaveIndex =
+ IterableUtils.firstIndexMatching(waves, wavePredicate.apply("writeFields"));
+ assertTrue(writeFieldsWaveIndex >= 0);
+ assertTrue(writeFieldsWaveIndex < readFieldsWaveIndex);
+ };
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNeverReprocessMethodAnnotations()
.setMinApi(parameters)
.compile()
.inspect(this::inspect)
@@ -75,7 +78,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
assertThat(testClassSubject.uniqueMethodWithOriginalName("live"), isPresent());
- assertThat(testClassSubject.uniqueMethodWithOriginalName("dead"), isAbsent());
+ assertThat(testClassSubject.uniqueMethodWithOriginalName("dead"), not(isPresent()));
}
static class TestClass {
@@ -97,10 +100,8 @@
static void increaseDistanceToNearestLeaf() {}
- // By processing writeFields() before readFields() and publishing the fact that alwaysFalse is
- // the constant false after processing writeFields(), it would be possible to optimize
- // readFields() in just a single optimization pass.
@NeverInline
+ @NeverReprocessMethod
static void readFields(A obj) {
if (alwaysFalse || obj.alwaysFalse) {
dead();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index 2f716b7..9fdff7c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -6,8 +6,6 @@
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.assertNotEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverClassInline;
@@ -76,12 +74,10 @@
MethodSubject testMaybeNullMethodSubject =
testClassSubject.uniqueMethodWithOriginalName("testMaybeNull");
assertThat(testMaybeNullMethodSubject, isPresent());
- // TODO(b/330674939): Should not have any instance-gets.
- assertEquals(
- parameters.canInitNewInstanceUsingSuperclassConstructor(),
+ assertTrue(
testMaybeNullMethodSubject
.streamInstructions()
- .anyMatch(InstructionSubject::isInstanceGet));
+ .noneMatch(InstructionSubject::isInstanceGet));
// TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
// ends up being unused.
assertTrue(
@@ -91,10 +87,7 @@
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
- // TODO(b/330674939): Should never have any instance fields.
- assertNotEquals(
- parameters.canInitNewInstanceUsingSuperclassConstructor(),
- aClassSubject.allInstanceFields().isEmpty());
+ assertTrue(aClassSubject.allInstanceFields().isEmpty());
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
index b79e37a..6272631 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
@@ -37,6 +37,7 @@
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> {
+ options.enableFieldAssignmentTracker = false;
options.enableFieldValueAnalysis = false;
})
.enableInliningAnnotations()