Reenable enum name() and toString() optimization
Bug: 160667929
Bug: 155239749
Change-Id: I1497485fe038873d290b641cb07633afc302763c
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 8b2bd65..aa1d5a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -462,7 +462,7 @@
public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
public final ClassMethods classMethods = new ClassMethods();
public final ConstructorMethods constructorMethods = new ConstructorMethods();
- public final EnumMethods enumMethods = new EnumMethods();
+ public final EnumMembers enumMembers = new EnumMembers();
public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
new IllegalArgumentExceptionMethods();
@@ -688,7 +688,7 @@
// If not, that library method should not be added here because it literally has side effects.
public Map<DexMethod, Predicate<InvokeMethod>> libraryMethodsWithoutSideEffects =
Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat(
- Stream.of(new Pair<>(enumMethods.constructor, alwaysTrue())),
+ Stream.of(new Pair<>(enumMembers.constructor, alwaysTrue())),
Stream.of(new Pair<>(npeMethods.init, alwaysTrue())),
Stream.of(new Pair<>(npeMethods.initWithMessage, alwaysTrue())),
Stream.of(new Pair<>(objectMembers.constructor, alwaysTrue())),
@@ -1268,11 +1268,14 @@
}
}
- public class EnumMethods {
+ public class EnumMembers {
+
+ public final DexField nameField = createField(enumType, stringType, "name");
+ public final DexField ordinalField = createField(enumType, intType, "ordinal");
public final DexMethod valueOf;
- public final DexMethod ordinal;
- public final DexMethod name;
+ public final DexMethod ordinalMethod;
+ public final DexMethod nameMethod;
public final DexMethod toString;
public final DexMethod compareTo;
public final DexMethod equals;
@@ -1283,25 +1286,17 @@
public final DexMethod finalize =
createMethod(enumType, createProto(voidType), finalizeMethodName);
- private EnumMethods() {
+ private EnumMembers() {
valueOf =
createMethod(
enumDescriptor,
valueOfMethodName,
enumDescriptor,
new DexString[] {classDescriptor, stringDescriptor});
- ordinal =
- createMethod(
- enumDescriptor,
- ordinalMethodName,
- intDescriptor,
- DexString.EMPTY_ARRAY);
- name =
- createMethod(
- enumDescriptor,
- nameMethodName,
- stringDescriptor,
- DexString.EMPTY_ARRAY);
+ ordinalMethod =
+ createMethod(enumDescriptor, ordinalMethodName, intDescriptor, DexString.EMPTY_ARRAY);
+ nameMethod =
+ createMethod(enumDescriptor, nameMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
toString =
createMethod(
enumDescriptor,
@@ -1321,6 +1316,15 @@
createMethod(enumDescriptor, hashCodeMethodName, intDescriptor, DexString.EMPTY_ARRAY);
}
+ public void forEachField(Consumer<DexField> fn) {
+ fn.accept(nameField);
+ fn.accept(ordinalField);
+ }
+
+ public boolean isNameOrOrdinalField(DexField field) {
+ return field == nameField || field == ordinalField;
+ }
+
public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
assert enumClass.isEnum();
return method.holder == enumClass.type
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 35d8146..7711eb3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -63,6 +63,10 @@
assert kind == Kind.CF : "Invalid kind " + kind + " for library-path class " + type;
}
+ public static DexLibraryClass asLibraryClassOrNull(DexClass clazz) {
+ return clazz != null ? clazz.asLibraryClass() : null;
+ }
+
private static boolean verifyLibraryMethod(DexEncodedMethod method) {
assert !method.isClassInitializer();
assert !method.isPrivateMethod();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 81df82a..3d8df1a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -229,7 +229,14 @@
initializationInfos.forEach(
appView,
(field, initializationInfo) -> {
- if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+ // If the instance field is not written only in the instance initializer, then we can't
+ // conclude that this field will have a constant value.
+ //
+ // We have special handling for library fields that satisfy the property that they are
+ // only written in their corresponding instance initializers. This is needed since we
+ // don't analyze these instance initializers in the Enqueuer, as they are in the library.
+ if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)
+ && !appView.dexItemFactory().enumMembers.isNameOrOrdinalField(field.toReference())) {
return;
}
if (initializationInfo.isArgumentInitializationInfo()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 81af63c..3b8fc7f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -189,7 +189,7 @@
.resolveMethodOnClass(dexItemFactory.objectMembers.finalize, clazz);
if (finalizeResolutionResult.isSingleResolution()) {
DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
- if (finalizeMethod != dexItemFactory.enumMethods.finalize
+ if (finalizeMethod != dexItemFactory.enumMembers.finalize
&& finalizeMethod != dexItemFactory.objectMembers.finalize) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c902868..0a82859 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -266,7 +266,7 @@
}
for (Instruction user : constClass.outValue().uniqueUsers()) {
if (!(user.isInvokeStatic()
- && user.asInvokeStatic().getInvokedMethod() == factory.enumMethods.valueOf)) {
+ && user.asInvokeStatic().getInvokedMethod() == factory.enumMembers.valueOf)) {
markEnumAsUnboxable(
Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
return;
@@ -401,7 +401,7 @@
// Enum candidates have necessarily only one constructor matching enumMethods.constructor
// signature.
- DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
+ DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMembers.constructor);
if (initializer == null) {
// This case typically happens when a programmer uses EnumSet/EnumMap without using the
// enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
@@ -482,19 +482,19 @@
return Reason.UNSUPPORTED_LIBRARY_CALL;
}
// TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
- if (singleTarget == factory.enumMethods.compareTo) {
+ if (singleTarget == factory.enumMembers.compareTo) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.equals) {
+ } else if (singleTarget == factory.enumMembers.equals) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.name) {
+ } else if (singleTarget == factory.enumMembers.nameMethod) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.toString) {
+ } else if (singleTarget == factory.enumMembers.toString) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.ordinal) {
+ } else if (singleTarget == factory.enumMembers.ordinalMethod) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.hashCode) {
+ } else if (singleTarget == factory.enumMembers.hashCode) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.constructor) {
+ } else if (singleTarget == factory.enumMembers.constructor) {
// Enum constructor call is allowed only if first call of an enum initializer.
if (code.method().isInstanceInitializer()
&& code.method().holder() == enumClass.type
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 08ba8b4..c4a6bb4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -83,7 +83,7 @@
private boolean isStandardEnumInitializer(DexEncodedMethod method) {
return method.isInstanceInitializer()
- && method.method.proto == factory.enumMethods.constructor.proto;
+ && method.method.proto == factory.enumMembers.constructor.proto;
}
// The enum should have the $VALUES static field and only fields directly referencing the enum
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 1832a2a..087e808 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -129,21 +129,21 @@
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
if (enumType != null) {
- if (invokedMethod == factory.enumMethods.ordinal
- || invokedMethod == factory.enumMethods.hashCode) {
+ if (invokedMethod == factory.enumMembers.ordinalMethod
+ || invokedMethod == factory.enumMembers.hashCode) {
replaceEnumInvoke(
iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
continue;
- } else if (invokedMethod == factory.enumMethods.equals) {
+ } else if (invokedMethod == factory.enumMembers.equals) {
replaceEnumInvoke(
iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
continue;
- } else if (invokedMethod == factory.enumMethods.compareTo) {
+ } else if (invokedMethod == factory.enumMembers.compareTo) {
replaceEnumInvoke(
iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
continue;
- } else if (invokedMethod == factory.enumMethods.name
- || invokedMethod == factory.enumMethods.toString) {
+ } else if (invokedMethod == factory.enumMembers.nameMethod
+ || invokedMethod == factory.enumMembers.toString) {
DexMethod toStringMethod = computeDefaultToStringUtilityMethod(enumType);
iterator.replaceCurrentInstruction(
new InvokeStatic(
@@ -155,7 +155,7 @@
} else if (instruction.isInvokeStatic()) {
InvokeStatic invokeStatic = instruction.asInvokeStatic();
DexMethod invokedMethod = invokeStatic.getInvokedMethod();
- if (invokedMethod == factory.enumMethods.valueOf
+ if (invokedMethod == factory.enumMembers.valueOf
&& invokeStatic.inValues().get(0).isConstClass()) {
DexType enumType =
invokeStatic.inValues().get(0).getConstInstruction().asConstClass().getValue();
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 789f4d9..ebe815d 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
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -18,6 +19,10 @@
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleStringValue;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -72,15 +77,9 @@
if (current.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
- boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
- boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
- boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
-
- // TODO(b/160667929): Re-enable name()/toString() optimizations.
- if (!isOrdinalInvoke) {
- continue;
- }
-
+ boolean isOrdinalInvoke = invokedMethod == factory.enumMembers.ordinalMethod;
+ boolean isNameInvoke = invokedMethod == factory.enumMembers.nameMethod;
+ boolean isToStringInvoke = invokedMethod == factory.enumMembers.toString;
if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
continue;
}
@@ -89,57 +88,98 @@
if (receiver.isPhi()) {
continue;
}
- Instruction definition = receiver.getDefinition();
- if (!definition.isStaticGet()) {
- continue;
- }
- DexField enumField = definition.asStaticGet().getField();
- EnumValueInfoMap valueInfoMap =
- appView.appInfo().withLiveness().getEnumValueInfoMap(enumField.type);
- if (valueInfoMap == null) {
+
+ StaticGet staticGet = receiver.getDefinition().asStaticGet();
+ if (staticGet == null) {
continue;
}
- // The receiver value is identified as being from a constant enum field lookup by the fact
- // that it is a static-get to a field whose type is the same as the enclosing class (which
- // is known to be an enum type). An enum may still define a static field using the enum type
- // so ensure the field is present in the ordinal map for final validation.
- EnumValueInfo valueInfo = valueInfoMap.getEnumValueInfo(enumField);
- if (valueInfo == null) {
+ DexField field = staticGet.getField();
+ DexEncodedField definition = field.lookupOnClass(appView.definitionForHolder(field));
+ if (definition == null) {
+ continue;
+ }
+
+ AbstractValue abstractValue = definition.getOptimizationInfo().getAbstractValue();
+ if (!abstractValue.isSingleFieldValue()) {
+ continue;
+ }
+
+ ObjectState objectState = abstractValue.asSingleFieldValue().getState();
+ if (objectState.isEmpty()) {
continue;
}
Value outValue = methodWithReceiver.outValue();
if (isOrdinalInvoke) {
- iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
- } else if (isNameInvoke) {
- Value newValue =
- code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
- iterator.replaceCurrentInstruction(
- new ConstString(
- newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
- newValue.addAffectedValuesTo(affectedValues);
- } else {
- assert isToStringInvoke;
- DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
- if (!enumClazz.accessFlags.isFinal()) {
- continue;
+ DexField ordinalField = appView.dexItemFactory().enumMembers.ordinalField;
+ DexEncodedField ordinalDefinition =
+ ordinalField.lookupOnClass(appView.definitionForHolder(ordinalField));
+ if (ordinalDefinition != null) {
+ SingleNumberValue ordinalValue =
+ objectState.getAbstractFieldValue(ordinalDefinition).asSingleNumberValue();
+ if (ordinalValue != null) {
+ iterator.replaceCurrentInstruction(
+ new ConstNumber(outValue, ordinalValue.getValue()));
+ }
}
- DexEncodedMethod singleTarget =
- appView
- .appInfo()
- .resolveMethodOnClass(factory.objectMembers.toString, valueInfo.type)
- .getSingleTarget();
- if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
- continue;
- }
- Value newValue =
- code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
- iterator.replaceCurrentInstruction(
- new ConstString(
- newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
- newValue.addAffectedValuesTo(affectedValues);
+ continue;
}
+
+ DexField nameField = appView.dexItemFactory().enumMembers.nameField;
+ DexEncodedField nameDefinition =
+ nameField.lookupOnClass(appView.definitionForHolder(nameField));
+ if (nameField == null) {
+ continue;
+ }
+
+ SingleStringValue nameValue =
+ objectState.getAbstractFieldValue(nameDefinition).asSingleStringValue();
+ if (nameValue == null) {
+ continue;
+ }
+
+ if (isNameInvoke) {
+ Value newValue =
+ code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
+ iterator.replaceCurrentInstruction(
+ new ConstString(
+ newValue,
+ nameValue.getDexString(),
+ ThrowingInfo.defaultForConstString(appView.options())));
+ newValue.addAffectedValuesTo(affectedValues);
+ continue;
+ }
+
+ assert isToStringInvoke;
+
+ DexClass enumClazz = appView.appInfo().definitionFor(field.type);
+ if (!enumClazz.isFinal()) {
+ continue;
+ }
+
+ EnumValueInfo valueInfo = appView.appInfo().withLiveness().getEnumValueInfo(field);
+ if (valueInfo == null) {
+ continue;
+ }
+
+ DexEncodedMethod singleTarget =
+ appView
+ .appInfo()
+ .resolveMethodOnClass(factory.objectMembers.toString, valueInfo.type)
+ .getSingleTarget();
+ if (singleTarget != null && singleTarget.method != factory.enumMembers.toString) {
+ continue;
+ }
+
+ Value newValue =
+ code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
+ iterator.replaceCurrentInstruction(
+ new ConstString(
+ newValue,
+ nameValue.getDexString(),
+ ThrowingInfo.defaultForConstString(appView.options())));
+ newValue.addAffectedValuesTo(affectedValues);
} else if (current.isArrayLength()) {
// Rewrites MyEnum.values().length to a constant int.
Instruction arrayDefinition = current.asArrayLength().array().getAliasedValue().definition;
@@ -148,7 +188,7 @@
DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder);
if (enumClass != null
&& enumClass.isEnum()
- && factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
+ && factory.enumMembers.isValuesMethod(invokedMethod, enumClass)) {
EnumValueInfoMap enumValueInfoMap =
appView.appInfo().withLiveness().getEnumValueInfoMap(invokedMethod.holder);
if (enumValueInfoMap != null) {
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 5542908..b189a57 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
@@ -563,7 +563,7 @@
return null;
}
// java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
- if (invokedMethod == dexItemFactory.enumMethods.constructor
+ if (invokedMethod == dexItemFactory.enumMembers.constructor
|| invokedMethod == dexItemFactory.objectMembers.constructor) {
builder.setParent(invokedMethod);
break;
@@ -1098,7 +1098,7 @@
.resolveMethodOnClass(appView.dexItemFactory().objectMembers.finalize, clazz);
DexEncodedMethod target = resolutionResult.getSingleTarget();
return target != null
- && target.method != dexItemFactory.enumMethods.finalize
+ && target.method != dexItemFactory.enumMembers.finalize
&& target.method != dexItemFactory.objectMembers.finalize;
}
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 822e0c5..62edebf 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
@@ -162,7 +162,7 @@
@Override
public void setInstanceInitializerInfo(
DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
- // Ignored.
+ method.getMutableOptimizationInfo().setInstanceInitializerInfo(instanceInitializerInfo);
}
@Override
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 d26de6d..5979031 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
@@ -47,9 +47,10 @@
recordInitializationInfo(field.field, info);
}
- public void recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
+ public Builder recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
assert !infos.containsKey(field);
infos.put(field, info);
+ return this;
}
public InstanceFieldInitializationInfoCollection build() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index cdc1793..658fcb5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -38,7 +38,7 @@
InvokeMethod invoke,
DexEncodedMethod singleTarget,
Set<Value> affectedValues) {
- if (singleTarget.method == appView.dexItemFactory().enumMethods.valueOf
+ if (singleTarget.method == appView.dexItemFactory().enumMembers.valueOf
&& invoke.inValues().get(0).isConstClass()) {
insertAssumeDynamicType(code, instructionIterator, invoke);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
new file mode 100644
index 0000000..3f3277f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
@@ -0,0 +1,47 @@
+// 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.optimize.library;
+
+import static com.android.tools.r8.graph.DexLibraryClass.asLibraryClassOrNull;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.FieldAccessFlags;
+
+/**
+ * This class synthesizes library fields that we rely on for modeling.
+ *
+ * <p>For example, we synthesize the field `java.lang.String java.lang.Enum.name` if it is not
+ * present. We use this to model that the constructor `void java.lang.Enum.<init>(java.lang.String,
+ * int)` initializes `java.lang.String java.lang.Enum.name` to the first argument of the
+ * constructor.
+ */
+public class LibraryFieldSynthesis {
+
+ public static void synthesizeEnumFields(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexLibraryClass enumClass =
+ asLibraryClassOrNull(appView.definitionFor(dexItemFactory.enumType));
+ if (enumClass != null) {
+ dexItemFactory.enumMembers.forEachField(
+ field -> {
+ DexEncodedField definition = enumClass.lookupField(field);
+ if (definition == null) {
+ enumClass.appendInstanceField(
+ new DexEncodedField(
+ field,
+ FieldAccessFlags.fromCfAccessFlags(
+ Constants.ACC_PRIVATE | Constants.ACC_FINAL),
+ DexAnnotationSet.empty(),
+ null));
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index e7e0fe6..d0412b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -11,12 +11,16 @@
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.DexItemFactory.EnumMembers;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
+import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
import com.google.common.collect.Sets;
import java.util.BitSet;
import java.util.Set;
@@ -38,6 +42,7 @@
}
void run(Set<DexEncodedField> finalLibraryFields) {
+ modelInstanceInitializers();
modelStaticFinalLibraryFields(finalLibraryFields);
modelLibraryMethodsReturningNonNull();
modelLibraryMethodsReturningReceiver();
@@ -48,6 +53,28 @@
return modeledLibraryTypes;
}
+ private void modelInstanceInitializers() {
+ EnumMembers enumMembers = dexItemFactory.enumMembers;
+ DexEncodedMethod enumConstructor = lookupMethod(enumMembers.constructor);
+ if (enumConstructor != null) {
+ LibraryFieldSynthesis.synthesizeEnumFields(appView);
+ InstanceFieldInitializationInfoFactory factory =
+ appView.instanceFieldInitializationInfoFactory();
+ InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+ InstanceFieldInitializationInfoCollection.builder()
+ .recordInitializationInfo(
+ enumMembers.nameField, factory.createArgumentInitializationInfo(1))
+ .recordInitializationInfo(
+ enumMembers.ordinalField, factory.createArgumentInitializationInfo(2))
+ .build();
+ feedback.setInstanceInitializerInfo(
+ enumConstructor,
+ NonTrivialInstanceInitializerInfo.builder(fieldInitializationInfos)
+ .setParent(dexItemFactory.objectMembers.constructor)
+ .build());
+ }
+ }
+
private void modelStaticFinalLibraryFields(Set<DexEncodedField> finalLibraryFields) {
for (DexEncodedField field : finalLibraryFields) {
if (field.isStatic()) {
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 5f68d46..e9d4c27 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.EnumValueInfoMapCollection;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -747,6 +748,12 @@
return enumValueInfoMaps.getEnumValueInfoMap(enumType);
}
+ public EnumValueInfo getEnumValueInfo(DexField field) {
+ assert checkIfObsolete();
+ EnumValueInfoMap map = enumValueInfoMaps.getEnumValueInfoMap(field.type);
+ return map != null ? map.getEnumValueInfo(field) : null;
+ }
+
public Int2ReferenceMap<DexField> getSwitchMap(DexField field) {
assert checkIfObsolete();
return switchMaps.get(field);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 6d4939f..df7690c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1119,7 +1119,7 @@
pendingReflectiveUses.add(context);
}
// See comment in handleJavaLangEnumValueOf.
- if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
+ if (invokedMethod == dexItemFactory.enumMembers.valueOf) {
pendingReflectiveUses.add(context);
}
// Handling of application services.
@@ -3666,7 +3666,7 @@
handleJavaLangReflectConstructorNewInstance(method, invoke);
return;
}
- if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
+ if (invokedMethod == dexItemFactory.enumMembers.valueOf) {
handleJavaLangEnumValueOf(method, invoke);
return;
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 1bf5f83..ae4947a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
import java.util.Objects;
-import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -75,14 +74,13 @@
if (enableOptimization) {
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("simple"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("local"), 1);
- // TODO(b/160667929): Re-enable name()/toString() optimizations.
// String concatenation optimization is enabled for DEX output.
// Even replaced ordinal is concatenated (and gone).
- // if (parameters.isDexRuntime()) {
- // assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
- // } else {
- assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
- // }
+ if (parameters.isDexRuntime()) {
+ assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
+ } else {
+ assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
+ }
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
@@ -123,7 +121,6 @@
assertTrue(clazz.isPresent());
if (enableOptimization) {
- Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
assertNameReplacedWithConst(clazz.uniqueMethodWithName("simple"), "TWO");
assertNameReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
// String concatenation optimization is enabled for DEX output.
@@ -174,7 +171,6 @@
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithoutToString"));
if (enableOptimization) {
- Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("noToString"), "TWO");
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), "TWO");