Compute dynamic type for each field
Change-Id: I9d622408f08ebeb672d75bd1fde1935ba8d235a4
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e21fbf4..1f13804 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepClassInfo;
+import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -533,6 +534,10 @@
return getKeepInfo().getClassInfo(clazz);
}
+ public KeepFieldInfo getKeepInfo(ProgramField field) {
+ return getKeepInfo().getFieldInfo(field);
+ }
+
public KeepMethodInfo getKeepInfo(ProgramMethod method) {
return getKeepInfo().getMethodInfo(method);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index b3e8f74..c8a9046 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -55,7 +55,11 @@
}
public TypeElement toTypeElement(AppView<?> appView) {
- return TypeElement.fromDexType(this, Nullability.maybeNull(), appView);
+ return toTypeElement(appView, Nullability.maybeNull());
+ }
+
+ public TypeElement toTypeElement(AppView<?> appView, Nullability nullability) {
+ return TypeElement.fromDexType(this, nullability, appView);
}
@Override
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 150f206..e5a1aec 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
@@ -85,8 +85,7 @@
resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
if (field != null) {
if (fieldAssignmentTracker != null) {
- fieldAssignmentTracker.recordFieldAccess(
- fieldInstruction, field.getDefinition(), code.context());
+ fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field, code.context());
}
if (fieldBitAccessAnalysis != null) {
fieldBitAccessAnalysis.recordFieldAccess(
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
index 70c8519..957c20e 100644
--- 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
@@ -4,19 +4,30 @@
package com.android.tools.r8.ir.analysis.fieldaccess;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+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.horizontalclassmerging.HorizontalClassMergerUtils;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteArrayTypeFieldState;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteClassTypeFieldState;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcretePrimitiveTypeFieldState;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.FieldState;
+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.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.NonConstantNumberValue;
import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -30,9 +41,10 @@
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.utils.WideningUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
@@ -40,13 +52,14 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
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
@@ -58,14 +71,17 @@
// sites have been seen when a class no longer has any incoming edges.
private final ObjectAllocationGraph objectAllocationGraph;
- // The set of fields that may store a non-zero value.
- private final Set<DexEncodedField> nonZeroFields = Sets.newConcurrentHashSet();
+ // 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, FieldState> fieldStates = new ConcurrentHashMap<>();
private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>>
abstractInstanceFieldValues = new ConcurrentHashMap<>();
FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
+ this.abstractValueFactory = appView.abstractValueFactory();
this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
this.fieldAccessGraph = new FieldAccessGraph();
this.objectAllocationGraph = new ObjectAllocationGraph();
}
@@ -109,33 +125,109 @@
}
abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass);
});
- }
-
- private boolean isAlwaysZero(DexEncodedField field) {
- return !appView.appInfo().isPinned(field.getReference()) && !nonZeroFields.contains(field);
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.forEachProgramField(
+ field -> {
+ FieldAccessInfo accessInfo = fieldAccessInfos.get(field.getReference());
+ KeepFieldInfo keepInfo = appView.getKeepInfo(field);
+ if (keepInfo.isPinned(appView.options()) || accessInfo.isWrittenFromMethodHandle()) {
+ fieldStates.put(field.getDefinition(), FieldState.unknown());
+ }
+ });
+ }
}
void acceptClassInitializerDefaultsResult(
ClassInitializerDefaultsResult classInitializerDefaultsResult) {
classInitializerDefaultsResult.forEachOptimizedField(
(field, value) -> {
- if (!value.isDefault(field.getReference().type)) {
- nonZeroFields.add(field);
+ 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();
+ ClassTypeElement nonNullableStringType =
+ dexItemFactory
+ .stringType
+ .toTypeElement(appView, definitelyNotNull())
+ .asClassType();
+ return ConcreteClassTypeFieldState.create(
+ abstractValue, DynamicType.createExact(nonNullableStringType));
+ } else {
+ assert fieldType.isPrimitiveType();
+ return ConcretePrimitiveTypeFieldState.create(abstractValue);
+ }
+ }
+ // If the field is already assigned outside the class initializer then just give up.
+ return FieldState.unknown();
+ });
});
}
- void recordFieldAccess(
- FieldInstruction instruction, DexEncodedField field, ProgramMethod context) {
+ void recordFieldAccess(FieldInstruction instruction, ProgramField field, ProgramMethod context) {
if (instruction.isFieldPut()) {
recordFieldPut(field, instruction.value(), context);
}
}
- private void recordFieldPut(DexEncodedField field, Value value, ProgramMethod context) {
- if (!value.isZero()) {
- nonZeroFields.add(field);
- }
+ private void recordFieldPut(ProgramField field, Value value, ProgramMethod context) {
+ // 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.createZeroValue() : AbstractValue.unknown();
+ fieldStates.compute(
+ field.getDefinition(),
+ (f, fieldState) -> {
+ if (fieldState == null || fieldState.isBottom()) {
+ DexType fieldType = field.getType();
+ if (fieldType.isArrayType()) {
+ return ConcreteArrayTypeFieldState.create(abstractValue);
+ }
+ if (fieldType.isPrimitiveType()) {
+ return ConcretePrimitiveTypeFieldState.create(abstractValue);
+ }
+ assert fieldType.isClassType();
+ DynamicType dynamicType =
+ fieldType.isArrayType()
+ ? DynamicType.unknown()
+ : WideningUtils.widenDynamicNonReceiverType(
+ appView,
+ value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
+ field.getType());
+ return ConcreteClassTypeFieldState.create(abstractValue, dynamicType);
+ }
+
+ if (fieldState.isUnknown()) {
+ return fieldState;
+ }
+
+ assert fieldState.isConcrete();
+
+ if (fieldState.isArray()) {
+ ConcreteArrayTypeFieldState arrayFieldState = fieldState.asArray();
+ return arrayFieldState.mutableJoin(appView, abstractValue);
+ }
+
+ if (fieldState.isPrimitive()) {
+ ConcretePrimitiveTypeFieldState primitiveFieldState = fieldState.asPrimitive();
+ return primitiveFieldState.mutableJoin(abstractValue, abstractValueFactory);
+ }
+
+ assert fieldState.isClass();
+
+ ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
+ return classFieldState.mutableJoin(
+ appView, abstractValue, value.getDynamicType(appView), field);
+ });
}
void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
@@ -146,7 +238,7 @@
return;
}
- InvokeDirect invoke = instruction.getUniqueConstructorInvoke(appView.dexItemFactory());
+ InvokeDirect invoke = instruction.getUniqueConstructorInvoke(dexItemFactory);
if (invoke == null) {
// We just lost track.
abstractInstanceFieldValues.remove(clazz);
@@ -238,27 +330,33 @@
}
private void recordAllFieldPutsProcessed(
- DexEncodedField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field, context));
- if (clazz == null) {
- assert false;
- return;
+ ProgramField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
+ FieldState fieldState = fieldStates.getOrDefault(field.getDefinition(), FieldState.bottom());
+ AbstractValue abstractValue = fieldState.getAbstractValue(appView.abstractValueFactory());
+ if (abstractValue.isNonTrivial()) {
+ feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
}
- if (isAlwaysZero(field)) {
- feedback.recordFieldHasAbstractValue(
- field, appView, appView.abstractValueFactory().createSingleNumberValue(0));
+ if (fieldState.isClass() && !field.getOptimizationInfo().hasDynamicUpperBoundType()) {
+ ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
+ DynamicType dynamicType = classFieldState.getDynamicType();
+ if (!dynamicType.isUnknown()) {
+ assert WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, field.getType())
+ == dynamicType;
+ feedback.markFieldHasDynamicType(field, dynamicType);
+ }
}
- if (!field.isStatic()) {
- recordAllInstanceFieldPutsProcessed(clazz, field, feedback);
+ if (!field.getAccessFlags().isStatic()) {
+ recordAllInstanceFieldPutsProcessed(field, feedback);
}
}
private void recordAllInstanceFieldPutsProcessed(
- DexProgramClass clazz, DexEncodedField field, OptimizationFeedbackDelayed feedback) {
+ ProgramField field, OptimizationFeedbackDelayed feedback) {
if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
AbstractValue abstractValue = BottomValue.getInstance();
+ DexProgramClass clazz = field.getHolder();
for (DexEncodedMethod method : clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
InstanceFieldInitializationInfo fieldInitializationInfo =
method
@@ -290,7 +388,7 @@
assert !abstractValue.isBottom();
if (!abstractValue.isUnknown()) {
- feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
}
}
}
@@ -334,8 +432,7 @@
static class FieldAccessGraph {
// The fields written by each method.
- private final Map<DexEncodedMethod, List<DexEncodedField>> fieldWrites =
- new IdentityHashMap<>();
+ 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 =
@@ -348,8 +445,7 @@
appView.appInfo().getFieldAccessInfoCollection();
fieldAccessInfoCollection.forEach(
info -> {
- DexEncodedField field =
- appView.appInfo().resolveField(info.getField()).getResolvedField();
+ ProgramField field = appView.appInfo().resolveField(info.getField()).getProgramField();
if (field == null) {
return;
}
@@ -359,18 +455,18 @@
fieldWrites
.computeIfAbsent(context.getDefinition(), ignore -> new ArrayList<>())
.add(field));
- pendingFieldWrites.put(field, info.getNumberOfWriteContexts());
+ pendingFieldWrites.put(field.getDefinition(), info.getNumberOfWriteContexts());
}
});
}
- void markProcessed(ProgramMethod method, Consumer<DexEncodedField> allWritesSeenConsumer) {
- List<DexEncodedField> fieldWritesInMethod = fieldWrites.get(method.getDefinition());
+ void markProcessed(ProgramMethod method, Consumer<ProgramField> allWritesSeenConsumer) {
+ List<ProgramField> fieldWritesInMethod = fieldWrites.get(method.getDefinition());
if (fieldWritesInMethod != null) {
- for (DexEncodedField field : fieldWritesInMethod) {
- int numberOfPendingFieldWrites = pendingFieldWrites.removeInt(field) - 1;
+ for (ProgramField field : fieldWritesInMethod) {
+ int numberOfPendingFieldWrites = pendingFieldWrites.removeInt(field.getDefinition()) - 1;
if (numberOfPendingFieldWrites > 0) {
- pendingFieldWrites.put(field, numberOfPendingFieldWrites);
+ pendingFieldWrites.put(field.getDefinition(), numberOfPendingFieldWrites);
} else {
allWritesSeenConsumer.accept(field);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index f25c73b..788e9cc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -261,6 +261,8 @@
assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
: "At " + this + System.lineSeparator() + outType + " != " + inType;
} else {
+ assert hasDynamicTypeAssumption();
+ assert !src().isConstNumber();
assert outType.equals(inType)
: "At " + this + System.lineSeparator() + outType + " != " + inType;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 38e86e1..36ec641 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -132,7 +132,9 @@
if (receiverLowerBoundType != null) {
DexType refinedReceiverType =
TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
- assert receiverLowerBoundType.getClassType() == refinedReceiverType
+ assert appViewWithLiveness
+ .appInfo()
+ .isSubtype(receiverLowerBoundType.getClassType(), refinedReceiverType)
|| appView.options().testing.allowTypeErrors
|| receiver.getDynamicUpperBoundType(appViewWithLiveness).isNullType()
|| receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index c6070c5..d91c889 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -6,7 +6,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.ProgramField;
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.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -19,6 +21,15 @@
void markFieldAsPropagated(DexEncodedField field);
+ default void markFieldHasDynamicType(ProgramField field, DynamicType dynamicType) {
+ TypeElement dynamicUpperBoundType = dynamicType.getDynamicUpperBoundType();
+ markFieldHasDynamicUpperBoundType(field.getDefinition(), dynamicUpperBoundType);
+ if (dynamicType.hasDynamicLowerBoundType()) {
+ ClassTypeElement dynamicLowerBoundType = dynamicType.getDynamicLowerBoundType();
+ markFieldHasDynamicLowerBoundType(field.getDefinition(), dynamicLowerBoundType);
+ }
+ }
+
void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeElement type);
void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeElement type);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
index f8abfc5..70057fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
@@ -62,7 +62,7 @@
Assume assumeInstruction = user.asAssume();
assumeInstruction.unsetDynamicTypeAssumption();
if (!assumeInstruction.hasNonNullAssumption()) {
- assumeInstruction.unsetDynamicTypeAssumption();
+ markForRemoval(assumeInstruction);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 206c455..755f334 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -411,7 +411,7 @@
// Verify that the optimization info is consistent with the static value.
assert definition.getOptimizationInfo().getAbstractValue().isUnknown()
|| !definition.hasExplicitStaticValue()
- || abstractValue == definition.getOptimizationInfo().getAbstractValue();
+ || abstractValue.equals(definition.getOptimizationInfo().getAbstractValue());
} else {
// This is guaranteed to read the default value of the field.
abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index ef3795d..442cb96 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -226,8 +226,9 @@
}
// Restore normality.
+ assumeRemover.removeMarkedInstructions();
code.removeAllDeadAndTrivialPhis(affectedValues);
- assumeRemover.removeMarkedInstructions().finish();
+ assumeRemover.finish();
assert code.isConsistentSSA();
rootsIterator.remove();
repeat = true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 8f42838..240beb2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -27,6 +27,10 @@
public abstract ClassTypeElement getDynamicLowerBoundType();
+ public final boolean hasDynamicUpperBoundType() {
+ return getDynamicUpperBoundType() != null;
+ }
+
public abstract TypeElement getDynamicUpperBoundType();
public ClassTypeElement getExactClassType(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index ef40285..c5d846e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.info.field;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
@@ -37,6 +38,10 @@
public abstract InstanceFieldInitializationInfo get(DexEncodedField field);
+ public final InstanceFieldInitializationInfo get(DexClassAndField field) {
+ return get(field.getDefinition());
+ }
+
public abstract boolean isEmpty();
public abstract InstanceFieldInitializationInfoCollection fixupAfterParametersChanged(
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 61c9077..3bd0c98 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
@@ -1060,6 +1061,10 @@
&& !fieldAccessInfo.isWrittenOutside(method);
}
+ public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexClassAndField field) {
+ return isInstanceFieldWrittenOnlyInInstanceInitializers(field.getDefinition());
+ }
+
public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexEncodedField field) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 0e373e9..f1498b6 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.AlwaysInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
@@ -51,6 +52,11 @@
" synthetic void registerObserver(...);",
"}")
.allowAccessModification()
+ .addAlwaysInliningAnnotations()
+ .addKeepRules(
+ "-alwaysinline class * { @"
+ + AlwaysInline.class.getTypeName()
+ + " !synthetic <methods>; }")
.enableNeverClassInliningAnnotations()
// TODO(b/120764902): MemberSubject.getOriginalName() is not working without the @NeverMerge
// annotation on DataAdapter.Observer.
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
index e6eceb7..b99ee02 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.bridgeremoval.bridgestokeep;
+import com.android.tools.r8.AlwaysInline;
import com.android.tools.r8.bridgeremoval.bridgestokeep.ObservableList.Observer;
import java.util.ArrayList;
import java.util.List;
@@ -13,6 +14,7 @@
private List<O> observers = new ArrayList<>();
+ @AlwaysInline
@Override
public void registerObserver(O observer) {
if (observer != null && observers != null && !observers.contains(observer)) {