Enum unboxing: Synthesize utility class per unboxed enum
Bug: 185224410
Change-Id: I2a7d6e95000bbcece1feddbfea93f0def6852fce
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 e9c4127..7bc7e56 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
@@ -38,7 +38,6 @@
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
@@ -128,8 +127,6 @@
// Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
// enum if the optimization eventually decides to unbox it.
private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
- private final ProgramPackageCollection enumsToUnboxWithPackageRequirement =
- ProgramPackageCollection.createEmpty();
private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
new ConcurrentHashMap<>();
private final ProgramMethodSet methodsDependingOnLibraryModelisation =
@@ -481,18 +478,15 @@
DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
FieldAccessInfoCollectionModifier.builder();
- UnboxedEnumMemberRelocator relocator =
- UnboxedEnumMemberRelocator.builder(appView)
+ EnumUnboxingUtilityClasses utilityClasses =
+ EnumUnboxingUtilityClasses.builder(appView)
.synthesizeEnumUnboxingUtilityClasses(
- enumClassesToUnbox,
- enumsToUnboxWithPackageRequirement,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder)
+ enumClassesToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder)
.build();
fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
- enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
+ enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, utilityClasses);
EnumUnboxingLens enumUnboxingLens =
- new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
+ new EnumUnboxingTreeFixer(appView, enumsToUnbox, utilityClasses, enumUnboxerRewriter)
.fixupTypeReferences();
enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
appView.setUnboxedEnums(enumDataMap);
@@ -539,7 +533,6 @@
public EnumDataMap finishAnalysis() {
analyzeInitializers();
- analyzeAccessibility();
EnumDataMap enumDataMap = analyzeEnumInstances();
if (debugLogEnabled) {
// Remove all enums that have been reported as being unboxable.
@@ -741,39 +734,6 @@
return OptionalInt.empty();
}
- private void analyzeAccessibility() {
- // Unboxing an enum will require to move its methods to a different class, which may impact
- // accessibility. For a quick analysis we simply reuse the inliner analysis.
- enumUnboxingCandidatesInfo.forEachCandidate(
- enumClass -> {
- Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
- if (classConstraint == Constraint.NEVER) {
- markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
- } else if (classConstraint == Constraint.PACKAGE) {
- enumsToUnboxWithPackageRequirement.addProgramClass(enumClass);
- }
- });
- }
-
- private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
- Constraint classConstraint = Constraint.ALWAYS;
- EnumAccessibilityUseRegistry useRegistry = null;
- for (DexEncodedMethod method : enumClass.methods()) {
- // Enum initializer are analyzed in analyzeInitializers instead.
- if (!method.isInitializer()) {
- if (useRegistry == null) {
- useRegistry = new EnumAccessibilityUseRegistry(factory);
- }
- Constraint methodConstraint = constraintForEnumUnboxing(method, useRegistry);
- classConstraint = classConstraint.meet(methodConstraint);
- if (classConstraint == Constraint.NEVER) {
- return classConstraint;
- }
- }
- }
- return classConstraint;
- }
-
public Constraint constraintForEnumUnboxing(
DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) {
return useRegistry.computeConstraint(method.asProgramMethod(appView));
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 bbe0796..9b159f2 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
@@ -71,8 +71,8 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
private final EnumDataMap unboxedEnumsData;
- private final UnboxedEnumMemberRelocator relocator;
private EnumUnboxingLens enumUnboxingLens;
+ private final EnumUnboxingUtilityClasses utilityClasses;
private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
@@ -86,44 +86,45 @@
EnumUnboxingRewriter(
AppView<AppInfoWithLiveness> appView,
EnumDataMap unboxedEnumsInstanceFieldData,
- UnboxedEnumMemberRelocator relocator) {
+ EnumUnboxingUtilityClasses utilityClasses) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
- this.relocator = relocator;
+ this.utilityClasses = utilityClasses;
// Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
- DexType defaultEnumUnboxingUtility = relocator.getDefaultEnumUnboxingUtility();
+ DexType sharedEnumUnboxingUtilityType =
+ utilityClasses.getSharedEnumUnboxingUtilityClass().getType();
this.ordinalUtilityMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
this.equalsUtilityMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.booleanType, factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
this.compareToUtilityMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
// Custom methods for generated field $VALUES initialization.
this.valuesUtilityMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.intArrayType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "values");
// Custom methods for Object#getClass without outValue and Objects.requireNonNull.
this.zeroCheckMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.voidType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
this.zeroCheckMessageMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.voidType, factory.intType, factory.stringType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
}
@@ -465,7 +466,8 @@
}
private DexField createValuesField(DexType enumType) {
- return createValuesField(enumType, relocator.getNewMemberLocationFor(enumType), factory);
+ return createValuesField(
+ enumType, utilityClasses.getLocalEnumUnboxingUtilityClass(enumType), factory);
}
static DexField createValuesField(
@@ -478,7 +480,7 @@
private DexMethod createValuesMethod(DexType enumType) {
return factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
+ utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
factory.createProto(factory.intArrayType),
"$$values$$method$" + compatibleName(enumType));
}
@@ -503,7 +505,7 @@
+ compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
+ utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
factory.createProto(field.type, factory.intType),
methodName);
utilityMethods.computeIfAbsent(
@@ -517,7 +519,7 @@
String methodName = "string$valueOf$" + compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
+ utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
factory.createProto(factory.stringType, factory.intType),
methodName);
AbstractValue nullString =
@@ -532,7 +534,7 @@
assert unboxedEnumsData.isUnboxedEnum(enumType);
DexMethod valueOf =
factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
+ utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
factory.createProto(factory.intType, factory.stringType),
"valueOf" + compatibleName(enumType));
utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index a29e290..d36d9b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -38,18 +38,18 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
private final Set<DexType> enumsToUnbox;
- private final UnboxedEnumMemberRelocator relocator;
+ private final EnumUnboxingUtilityClasses utilityClasses;
private final EnumUnboxingRewriter enumUnboxerRewriter;
EnumUnboxingTreeFixer(
AppView<AppInfoWithLiveness> appView,
Set<DexType> enumsToUnbox,
- UnboxedEnumMemberRelocator relocator,
+ EnumUnboxingUtilityClasses utilityClasses,
EnumUnboxingRewriter enumUnboxerRewriter) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.enumsToUnbox = enumsToUnbox;
- this.relocator = relocator;
+ this.utilityClasses = utilityClasses;
this.enumUnboxerRewriter = enumUnboxerRewriter;
}
@@ -57,7 +57,7 @@
assert enumUnboxerRewriter != null;
// Fix all methods and fields using enums to unbox.
for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (enumsToUnbox.contains(clazz.type)) {
+ if (enumsToUnbox.contains(clazz.getType())) {
// Clear the initializers and move the static methods to the new location.
Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
clazz
@@ -67,7 +67,7 @@
if (m.isInitializer()) {
clearEnumToUnboxMethod(m);
} else {
- DexType newHolder = relocator.getNewMemberLocationFor(clazz.type);
+ DexType newHolder = utilityClasses.getLocalEnumUnboxingUtilityClass(clazz);
List<DexEncodedMethod> movedMethods =
unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
@@ -178,16 +178,15 @@
newMethod =
factory.createInstanceInitializerWithFreshProto(
newMethod,
- relocator.getDefaultEnumUnboxingUtility(),
+ utilityClasses.getSharedEnumUnboxingUtilityClass().getType(),
tryMethod -> holder.lookupMethod(tryMethod) == null);
} else {
int index = 0;
while (holder.lookupMethod(newMethod) != null) {
newMethod =
- factory.createMethod(
- newMethod.holder,
- newMethod.proto,
- encodedMethod.getName().toString() + "$enumunboxing$" + index++);
+ newMethod.withName(
+ encodedMethod.getName().toString() + "$enumunboxing$" + index++,
+ appView.dexItemFactory());
}
}
return newMethod;
@@ -202,7 +201,7 @@
DexField field = encodedField.getReference();
DexType newType = fixupType(field.type);
if (newType != field.type) {
- DexField newField = factory.createField(field.holder, newType, field.name);
+ DexField newField = field.withType(newType, factory);
lensBuilder.move(field, newField);
DexEncodedField newEncodedField =
encodedField.toTypeSubstitutedField(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
new file mode 100644
index 0000000..dda4779
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -0,0 +1,209 @@
+// 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.enums;
+
+import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class EnumUnboxingUtilityClasses {
+
+ public static final String ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX =
+ "$r8$EnumUnboxingLocalUtility";
+ public static final String ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX =
+ "$r8$EnumUnboxingSharedUtility";
+
+ // Synthetic classes for utilities specific to the unboxing of a single enum.
+ private final ImmutableMap<DexType, DexProgramClass> localEnumUnboxingUtilityClasses;
+
+ // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
+ // methods (ordinal(I), equals(II), etc.).
+ private final DexProgramClass sharedEnumUnboxingUtilityClass;
+
+ private EnumUnboxingUtilityClasses(
+ DexProgramClass sharedEnumUnboxingUtilityClass,
+ ImmutableMap<DexType, DexProgramClass> localEnumUnboxingUtilityClasses) {
+ this.sharedEnumUnboxingUtilityClass = sharedEnumUnboxingUtilityClass;
+ this.localEnumUnboxingUtilityClasses = localEnumUnboxingUtilityClasses;
+ }
+
+ public DexType getLocalEnumUnboxingUtilityClass(DexProgramClass enumClass) {
+ return getLocalEnumUnboxingUtilityClass(enumClass.getType());
+ }
+
+ public DexType getLocalEnumUnboxingUtilityClass(DexType enumType) {
+ DexProgramClass localEnumUnboxingUtilityClass = localEnumUnboxingUtilityClasses.get(enumType);
+ assert localEnumUnboxingUtilityClass != null;
+ return localEnumUnboxingUtilityClass.getType();
+ }
+
+ public DexProgramClass getSharedEnumUnboxingUtilityClass() {
+ return sharedEnumUnboxingUtilityClass;
+ }
+
+ public static Builder builder(AppView<AppInfoWithLiveness> appView) {
+ return new Builder(appView);
+ }
+
+ public static class Builder {
+
+ private final AppView<?> appView;
+ private final Map<DexType, DexProgramClass> localEnumUnboxingUtilityClasses =
+ new IdentityHashMap<>();
+ private DexProgramClass sharedEnumUnboxingUtilityClass;
+
+ public Builder(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public Builder synthesizeEnumUnboxingUtilityClasses(
+ Set<DexProgramClass> enumsToUnbox,
+ DirectMappedDexApplication.Builder appBuilder,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ synthesizeLocalUtilityClasses(
+ enumsToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder);
+ synthesizeSharedUtilityClass(enumsToUnbox, appBuilder);
+ return this;
+ }
+
+ public EnumUnboxingUtilityClasses build() {
+ return new EnumUnboxingUtilityClasses(
+ sharedEnumUnboxingUtilityClass, ImmutableMap.copyOf(localEnumUnboxingUtilityClasses));
+ }
+
+ private void synthesizeLocalUtilityClasses(
+ Set<DexProgramClass> enumsToUnbox,
+ DirectMappedDexApplication.Builder appBuilder,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ for (DexProgramClass enumToUnbox : enumsToUnbox) {
+ synthesizeLocalUtilityClass(
+ enumToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder);
+ }
+ }
+
+ private void synthesizeLocalUtilityClass(
+ DexProgramClass enumToUnbox,
+ DirectMappedDexApplication.Builder appBuilder,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ DexType localUtilityClassType = getLocalUtilityClassType(enumToUnbox);
+ assert appView.appInfo().definitionForWithoutExistenceAssert(localUtilityClassType) == null;
+
+ // Required fields.
+ DexField reference =
+ createValuesField(enumToUnbox.getType(), localUtilityClassType, appView.dexItemFactory());
+ DexEncodedField staticField =
+ new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic());
+ fieldAccessInfoCollectionModifierBuilder
+ .recordFieldReadInUnknownContext(reference)
+ .recordFieldWriteInUnknownContext(reference);
+
+ DexProgramClass localUtilityClass =
+ new DexProgramClass(
+ localUtilityClassType,
+ null,
+ new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+ ClassAccessFlags.createPublicFinalSynthetic(),
+ appView.dexItemFactory().objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ ClassSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ new DexEncodedField[] {staticField},
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ appView.dexItemFactory().getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ appBuilder.addSynthesizedClass(localUtilityClass);
+ appView.appInfo().addSynthesizedClass(localUtilityClass, enumToUnbox);
+ localEnumUnboxingUtilityClasses.put(enumToUnbox.getType(), localUtilityClass);
+ }
+
+ private void synthesizeSharedUtilityClass(
+ Set<DexProgramClass> enumsToUnbox, DirectMappedDexApplication.Builder appBuilder) {
+ DexType type = getSharedUtilityClassType(findDeterministicContextType(enumsToUnbox));
+ assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
+
+ DexProgramClass syntheticClass =
+ new DexProgramClass(
+ type,
+ null,
+ new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+ ClassAccessFlags.createPublicFinalSynthetic(),
+ appView.dexItemFactory().objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ ClassSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ appView.dexItemFactory().getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ appBuilder.addSynthesizedClass(syntheticClass);
+ appView.appInfo().addSynthesizedClassToBase(syntheticClass, enumsToUnbox);
+ sharedEnumUnboxingUtilityClass = syntheticClass;
+ }
+
+ private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
+ DexProgramClass deterministicContext = null;
+ for (DexProgramClass context : contexts) {
+ if (deterministicContext == null) {
+ deterministicContext = context;
+ } else if (context.type.compareTo(deterministicContext.type) < 0) {
+ deterministicContext = context;
+ }
+ }
+ return deterministicContext;
+ }
+
+ private DexType getLocalUtilityClassType(DexProgramClass context) {
+ return getUtilityClassType(context, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX);
+ }
+
+ private DexType getSharedUtilityClassType(DexProgramClass context) {
+ return getUtilityClassType(context, ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX);
+ }
+
+ private DexType getUtilityClassType(DexProgramClass context, String suffix) {
+ return appView
+ .dexItemFactory()
+ .createType(
+ DescriptorUtils.getDescriptorFromClassBinaryName(
+ DescriptorUtils.getBinaryNameFromDescriptor(
+ context.getType().toDescriptorString())
+ + suffix));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
deleted file mode 100644
index 54c416a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ /dev/null
@@ -1,198 +0,0 @@
-// 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.enums;
-
-import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-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.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.ProgramPackage;
-import com.android.tools.r8.graph.ProgramPackageCollection;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableMap;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class UnboxedEnumMemberRelocator {
-
- public static final String ENUM_UNBOXING_UTILITY_CLASS_SUFFIX = "$r8$EnumUnboxingUtility";
-
- // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
- // methods (ordinal(I), equals(II), etc.) and the unboxed enums members which were free to be
- // placed anywhere.
- private final DexType defaultEnumUnboxingUtility;
- // Some unboxed enum members have to be placed in a specific package, in this case, we keep a
- // map from unboxed enum types to synthetic classes, so that all members of unboxed enums in the
- // keys are moved to the corresponding value.
- private final ImmutableMap<DexType, DexType> relocationMap;
-
- public DexType getDefaultEnumUnboxingUtility() {
- return defaultEnumUnboxingUtility;
- }
-
- public DexType getNewMemberLocationFor(DexType enumType) {
- return relocationMap.getOrDefault(enumType, defaultEnumUnboxingUtility);
- }
-
- private UnboxedEnumMemberRelocator(
- DexType defaultEnumUnboxingUtility, ImmutableMap<DexType, DexType> relocationMap) {
- this.defaultEnumUnboxingUtility = defaultEnumUnboxingUtility;
- this.relocationMap = relocationMap;
- }
-
- public static Builder builder(AppView<?> appView) {
- return new Builder(appView);
- }
-
- public static class Builder {
- private DexProgramClass defaultEnumUnboxingUtility;
- private Map<DexType, DexType> relocationMap = new IdentityHashMap<>();
- private final AppView<?> appView;
-
- public Builder(AppView<?> appView) {
- this.appView = appView;
- }
-
- public Builder synthesizeEnumUnboxingUtilityClasses(
- Set<DexProgramClass> enumsToUnbox,
- ProgramPackageCollection enumsToUnboxWithPackageRequirement,
- DirectMappedDexApplication.Builder appBuilder,
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- Set<DexProgramClass> enumsToUnboxWithoutPackageRequirement =
- SetUtils.newIdentityHashSet(enumsToUnbox);
- enumsToUnboxWithoutPackageRequirement.removeIf(enumsToUnboxWithPackageRequirement::contains);
- defaultEnumUnboxingUtility =
- synthesizeUtilityClass(
- enumsToUnbox,
- enumsToUnboxWithoutPackageRequirement,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder);
- if (!enumsToUnboxWithPackageRequirement.isEmpty()) {
- synthesizeRelocationMap(
- enumsToUnbox,
- enumsToUnboxWithPackageRequirement,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder);
- }
- return this;
- }
-
- public UnboxedEnumMemberRelocator build() {
- return new UnboxedEnumMemberRelocator(
- defaultEnumUnboxingUtility.getType(), ImmutableMap.copyOf(relocationMap));
- }
-
- private void synthesizeRelocationMap(
- Set<DexProgramClass> contexts,
- ProgramPackageCollection enumsToUnboxWithPackageRequirement,
- DirectMappedDexApplication.Builder appBuilder,
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- for (ProgramPackage programPackage : enumsToUnboxWithPackageRequirement) {
- Set<DexProgramClass> enumsToUnboxInPackage = programPackage.classesInPackage();
- DexProgramClass enumUtilityClass =
- synthesizeUtilityClass(
- contexts,
- enumsToUnboxInPackage,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder);
- if (enumUtilityClass != defaultEnumUnboxingUtility) {
- for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
- assert !relocationMap.containsKey(enumToUnbox.type);
- relocationMap.put(enumToUnbox.type, enumUtilityClass.getType());
- }
- }
- }
- }
-
- private DexProgramClass synthesizeUtilityClass(
- Set<DexProgramClass> contexts,
- Set<DexProgramClass> relocatedEnums,
- DirectMappedDexApplication.Builder appBuilder,
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- DexProgramClass deterministicContext = findDeterministicContextType(contexts);
- String descriptorString = deterministicContext.getType().toDescriptorString();
- String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
- String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
- DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
-
- // Required fields.
- List<DexEncodedField> staticFields = new ArrayList<>(relocatedEnums.size());
- for (DexProgramClass relocatedEnum : relocatedEnums) {
- DexField reference =
- createValuesField(relocatedEnum.getType(), type, appView.dexItemFactory());
- staticFields.add(
- new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic()));
- fieldAccessInfoCollectionModifierBuilder
- .recordFieldReadInUnknownContext(reference)
- .recordFieldWriteInUnknownContext(reference);
- }
- staticFields.sort(Comparator.comparing(DexEncodedField::getReference));
-
- // The defaultEnumUnboxingUtility depends on all unboxable enums, and other synthetic types
- // depend on a subset of the unboxable enums, the deterministicContextType can therefore
- // be found twice, and in that case the same utility class can be used for both.
- if (defaultEnumUnboxingUtility != null && type == defaultEnumUnboxingUtility.getType()) {
- defaultEnumUnboxingUtility.appendStaticFields(staticFields);
- return defaultEnumUnboxingUtility;
- }
- assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
- DexProgramClass syntheticClass =
- new DexProgramClass(
- type,
- null,
- new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
- ClassAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- null,
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
- appBuilder.addSynthesizedClass(syntheticClass);
- appView.appInfo().addSynthesizedClassToBase(syntheticClass, contexts);
- return syntheticClass;
- }
-
- private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
- DexProgramClass deterministicContext = null;
- for (DexProgramClass context : contexts) {
- if (deterministicContext == null) {
- deterministicContext = context;
- } else if (context.type.compareTo(deterministicContext.type) < 0) {
- deterministicContext = context;
- }
- }
- return deterministicContext;
- }
- }
-}
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 4378c0f..75bb5f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType;
-import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
@@ -1744,7 +1743,6 @@
assert !mode.isFinalMainDexTracing()
|| !options.testing.checkForNotExpandingMainDexTracingResult
|| appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
- || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
: "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
// Mark types in inner-class attributes referenced.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index 0326e0b..faf1061 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -8,9 +8,8 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.enumunboxing.DoubleProcessingEnumUnboxingTest.App.AppEnum;
import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingUtilityClasses;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -78,19 +77,20 @@
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::assertUtilityClassPresent)
+ .inspect(this::assertSharedUtilityClassPresent)
.run(parameters.getRuntime(), App.class)
.assertSuccess()
.inspectStdOut(this::assertLines2By2Correct);
}
- private void assertUtilityClassPresent(CodeInspector codeInspector) {
+ private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
assertTrue(
codeInspector.allClasses().stream()
.anyMatch(
c ->
c.getOriginalName()
- .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+ .contains(
+ EnumUnboxingUtilityClasses.ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
}
static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index 44cdff0..58d220f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
import com.android.tools.r8.enumunboxing.examplelib2.JavaLibrary2;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingUtilityClasses;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -68,7 +68,7 @@
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::assertUtilityClassPresent)
+ .inspect(this::assertSharedUtilityClassPresent)
.run(parameters.getRuntime(), App.class)
.assertSuccess()
.inspectStdOut(this::assertLines2By2Correct);
@@ -88,13 +88,14 @@
.writeToZip();
}
- private void assertUtilityClassPresent(CodeInspector codeInspector) {
+ private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
assertTrue(
codeInspector.allClasses().stream()
.anyMatch(
c ->
c.getOriginalName()
- .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+ .contains(
+ EnumUnboxingUtilityClasses.ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
}
static class App {