Enum unboxing: support String#valueOf
Bug: 166397278
Change-Id: Ieee183d7676b0d933cd011d509dc5ccbd6b7be38
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 3d7d397..eea90f9 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
@@ -877,6 +877,10 @@
if (singleTarget == factory.javaLangSystemMethods.identityHashCode) {
return Reason.ELIGIBLE;
}
+ if (singleTarget == factory.stringMembers.valueOf) {
+ requiredEnumInstanceFieldData(enumClass.type, factory.enumMembers.nameField);
+ return Reason.ELIGIBLE;
+ }
if (singleTarget == factory.objectMembers.getClass
&& (!invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers())) {
// This is a hidden null check.
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 ca106ab..b488c5b 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
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.ArrayAccess;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
@@ -205,15 +206,26 @@
continue;
}
} else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
- assert invokeStatic.inValues().size() == 1;
+ assert invokeStatic.arguments().size() == 1;
Value argument = invokeStatic.getArgument(0);
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
invokeStatic.outValue().replaceUsers(argument);
iterator.removeOrReplaceByDebugLocalRead();
}
+ } else if (invokedMethod == factory.stringMembers.valueOf) {
+ assert invokeStatic.arguments().size() == 1;
+ Value argument = invokeStatic.getArgument(0);
+ DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+ if (enumType != null) {
+ DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumType);
+ iterator.replaceCurrentInstruction(
+ new InvokeStatic(
+ stringValueOfMethod, invokeStatic.outValue(), invokeStatic.arguments()));
+ continue;
+ }
} else if (invokedMethod == factory.objectsMethods.requireNonNull) {
- assert invokeStatic.inValues().size() == 1;
+ assert invokeStatic.arguments().size() == 1;
Value argument = invokeStatic.getArgument(0);
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
@@ -221,7 +233,7 @@
iterator, invokeStatic, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
}
} else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
- assert invokeStatic.inValues().size() == 2;
+ assert invokeStatic.arguments().size() == 2;
Value argument = invokeStatic.getArgument(0);
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
@@ -407,7 +419,24 @@
factory.createProto(field.type, factory.intType),
methodName);
utilityMethods.computeIfAbsent(
- fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field));
+ fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field, null));
+ return fieldMethod;
+ }
+
+ private DexMethod computeStringValueOfUtilityMethod(DexType enumType) {
+ // TODO(b/167994636): remove duplication between instance field name read and this method.
+ assert enumsToUnbox.containsEnum(enumType);
+ String methodName = "string$valueOf$" + compatibleName(enumType);
+ DexMethod fieldMethod =
+ factory.createMethod(
+ factory.enumUnboxingUtilityType,
+ factory.createProto(factory.stringType, factory.intType),
+ methodName);
+ AbstractValue nullString =
+ appView.abstractValueFactory().createSingleStringValue(factory.createString("null"));
+ utilityMethods.computeIfAbsent(
+ fieldMethod,
+ m -> synthesizeInstanceFieldMethod(m, enumType, factory.enumMembers.nameField, nullString));
return fieldMethod;
}
@@ -489,7 +518,7 @@
}
private DexEncodedMethod synthesizeInstanceFieldMethod(
- DexMethod method, DexType enumType, DexField field) {
+ DexMethod method, DexType enumType, DexField field, AbstractValue nullValue) {
assert method.proto.returnType == field.type;
assert unboxedEnumsInstanceFieldData.getInstanceFieldData(enumType, field).isMapping();
CfCode cfCode =
@@ -500,7 +529,8 @@
enumsToUnbox.getEnumValueInfoMap(enumType),
unboxedEnumsInstanceFieldData
.getInstanceFieldData(enumType, field)
- .asEnumFieldMappingData())
+ .asEnumFieldMappingData(),
+ nullValue)
.generateCfCode();
return synthesizeUtilityMethod(cfCode, method, false);
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 4d15f0f..52cdb30 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -66,17 +66,20 @@
private final DexType returnType;
private final EnumValueInfoMap enumValueInfoMap;
private final EnumInstanceFieldMappingData fieldDataMap;
+ private final AbstractValue nullValue;
public EnumUnboxingInstanceFieldCfCodeProvider(
AppView<?> appView,
DexType holder,
DexType returnType,
EnumValueInfoMap enumValueInfoMap,
- EnumInstanceFieldMappingData fieldDataMap) {
+ EnumInstanceFieldMappingData fieldDataMap,
+ AbstractValue nullValue) {
super(appView, holder);
this.returnType = returnType;
this.enumValueInfoMap = enumValueInfoMap;
this.fieldDataMap = fieldDataMap;
+ this.nullValue = nullValue;
}
@Override
@@ -113,9 +116,15 @@
}
});
- // throw null;
- instructions.add(new CfConstNull());
- instructions.add(new CfThrow());
+ if (nullValue != null) {
+ // return "null"
+ addCfInstructionsForAbstractValue(instructions, nullValue, returnType);
+ instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
+ } else {
+ // throw null;
+ instructions.add(new CfConstNull());
+ instructions.add(new CfThrow());
+ }
return standardCfCodeFromInstructions(instructions);
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
new file mode 100644
index 0000000..1ab86b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
@@ -0,0 +1,77 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StringValueOfEnumUnboxingTest extends EnumUnboxingTestBase {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public StringValueOfEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = Main.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classToTest, MyEnum.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ System.out.println(MyEnum.A.ordinal());
+ System.out.println(0);
+ System.out.println(getString(MyEnum.A));
+ System.out.println("A");
+ System.out.println(getString(null));
+ System.out.println("null");
+ }
+
+ @NeverInline
+ private static String getString(MyEnum e) {
+ return String.valueOf(e);
+ }
+ }
+}