Use arraycopy to create enum values array in enum unboxing
This changes such that a shared utility class now contains (n being the largest enum value of any unboxed enum):
public static final synthetic $VALUES =
new int[] { 1, 2, 3, ..., n };
Accesses to MyEnum.$VALUES and calls to MyEnum.values() are rewritten to MyEnum$LocalUtility.values() (m being the largest enum value of MyEnum):
public static final synthetic values() {
int[] result = new int[m];
System.arraycopy($VALUES, 0, result, 0, m);
return result;
}
This returns a copy of the underlying $VALUES array when accessing it via a field read. The $VALUES array generally only has a single use, which is followed by a call to clone(). This CL therefore identifies the call to clone() and removes it.
As a result of this, MyEnum.$VALUES == MyEnum.$VALUES will return false (but Arrays.equals(MyEnum$VALUES, MyEnum$VALUES) is true). If this turns out to be a problem, we can consider disabling enum unboxing of enums that have unsupported reads of $VALUES.
Change-Id: I543fb1b73bf34faa12486d00137ca1d0d10cf863
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 14f0015..3d1a246 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -151,6 +151,17 @@
private StackMapStatus stackMapStatus = StackMapStatus.NOT_VERIFIED;
public CfCode(
+ DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) {
+ this(
+ originalHolder,
+ maxStack,
+ maxLocals,
+ instructions,
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ public CfCode(
DexType originalHolder,
int maxStack,
int maxLocals,
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 0611cdd5..da8323c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -31,6 +31,10 @@
public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField>
implements StructuralItem<DexEncodedField> {
+
+ public static final boolean D8_R8_SYNTHESIZED = true;
+ public static final boolean NOT_DEPRECATED = false;
+ public static final DexValue NO_STATIC_VALUE = null;
public static final DexEncodedField[] EMPTY_ARRAY = {};
public final FieldAccessFlags accessFlags;
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 dedc362..d7a48c1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1511,16 +1511,16 @@
}
public class JavaLangSystemMethods {
- public final DexMethod identityHashCode;
- private JavaLangSystemMethods() {
- identityHashCode =
- createMethod(
- javaLangSystemDescriptor,
- identityHashCodeName,
- intDescriptor,
- new DexString[] {objectDescriptor});
- }
+ public final DexMethod arraycopy =
+ createMethod(
+ javaLangSystemType,
+ createProto(voidType, objectType, intType, objectType, intType, intType),
+ "arraycopy");
+ public final DexMethod identityHashCode =
+ createMethod(javaLangSystemType, createProto(intType, objectType), identityHashCodeName);
+
+ private JavaLangSystemMethods() {}
}
public class EnumMembers {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 1697f43..850c3cb 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -58,6 +58,14 @@
return this;
}
+ public static FieldAccessFlags createPublicStaticFinalSynthetic() {
+ return fromSharedAccessFlags(
+ Constants.ACC_PUBLIC
+ | Constants.ACC_STATIC
+ | Constants.ACC_FINAL
+ | Constants.ACC_SYNTHETIC);
+ }
+
public static FieldAccessFlags createPublicStaticSynthetic() {
return fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 21608cf..44896ea 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -73,34 +73,22 @@
this.defaultCodeOptimizations = defaultCodeOptimizations;
}
- private void put(
- ProgramMethodSet methodsToRevisit, Collection<CodeOptimization> codeOptimizations) {
- if (codeOptimizations.isEmpty()) {
- // Nothing to conduct.
- return;
- }
- for (ProgramMethod method : methodsToRevisit) {
- methodsToReprocess.add(method);
- optimizationsMap
- .computeIfAbsent(
- method.getReference(),
- // Optimization order might matter, hence a collection that preserves orderings.
- k -> new LinkedHashSet<>())
- .addAll(codeOptimizations);
- }
+ public void add(ProgramMethod method) {
+ methodsToReprocess.add(method);
+ optimizationsMap
+ .computeIfAbsent(
+ method.getReference(),
+ // Optimization order might matter, hence a collection that preserves orderings.
+ k -> new LinkedHashSet<>())
+ .addAll(defaultCodeOptimizations);
}
public void put(ProgramMethodSet methodsToRevisit) {
- put(methodsToRevisit, defaultCodeOptimizations);
+ methodsToRevisit.forEach(this::add);
}
public void put(PostOptimization postOptimization) {
- Collection<CodeOptimization> codeOptimizations =
- postOptimization.codeOptimizationsForPostProcessing();
- if (codeOptimizations == null) {
- codeOptimizations = defaultCodeOptimizations;
- }
- put(postOptimization.methodsToRevisit(), codeOptimizations);
+ put(postOptimization.methodsToRevisit());
}
// Some optimizations may change methods, creating new instances of the encoded methods with a
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
index 7f06207..30c873d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.Collection;
/**
* An abstraction of optimizations that require post processing of methods.
@@ -13,16 +12,4 @@
/** @return a set of methods that need post processing. */
ProgramMethodSet methodsToRevisit();
-
- // TODO(b/127694949): different CodeOptimization for primary processor v.s. post processor?
- // In that way, instead of internal state changes, such as COLLECT v.s. APPLY or REVISIT,
- // optimizers that need post processing can return what to do at each processor.
- // Collection<CodeOptimization> codeOptimizationsForPrimaryProcessing();
-
- /**
- * @return specific collection of {@link CodeOptimization}s to conduct during post processing.
- * Otherwise, i.e., if the default one---IRConverter's full processing---is okay,
- * returns {@code null}.
- */
- Collection<CodeOptimization> codeOptimizationsForPostProcessing();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 3403e7b..2f04f11 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -27,7 +27,6 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CodeOptimization;
import com.android.tools.r8.ir.conversion.PostOptimization;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
@@ -42,7 +41,6 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
-import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -490,12 +488,6 @@
return targetsToRevisit;
}
- @Override
- public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
- // Run IRConverter#optimize.
- return null;
- }
-
private synchronized boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
InvokeMethod invoke, ProgramMethod context) {
ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a6f246f..9e82361 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -46,7 +46,6 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CodeOptimization;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostOptimization;
@@ -71,7 +70,6 @@
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
@@ -244,12 +242,6 @@
return doubleInlineCallers;
}
- @Override
- public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
- // Run IRConverter#optimize.
- return null; // Technically same as return converter.getOptimizationForPostIRProcessing();
- }
-
/**
* Encodes the constraints for inlining a method's instructions into a different context.
* <p>
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index 07471e7..b47ec05 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.enums;
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.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
import com.google.common.collect.ImmutableMap;
@@ -30,6 +31,12 @@
return map.isEmpty();
}
+ public EnumData get(DexProgramClass enumClass) {
+ EnumData enumData = map.get(enumClass.getType());
+ assert enumData != null;
+ return enumData;
+ }
+
public Set<DexType> getUnboxedEnums() {
return map.keySet();
}
@@ -55,6 +62,16 @@
return map.get(enumType).getValuesSize();
}
+ public int getMaxValuesSize() {
+ int maxValuesSize = 0;
+ for (EnumData data : map.values()) {
+ if (data.hasValues()) {
+ maxValuesSize = Math.max(maxValuesSize, data.getValuesSize());
+ }
+ }
+ return maxValuesSize;
+ }
+
public boolean matchesValuesField(DexField staticField) {
assert map.containsKey(staticField.holder);
return map.get(staticField.holder).matchesValuesField(staticField);
@@ -101,8 +118,12 @@
return valuesFields.contains(field);
}
+ public boolean hasValues() {
+ return valuesSize != INVALID_VALUES_SIZE;
+ }
+
public int getValuesSize() {
- assert valuesSize != INVALID_VALUES_SIZE;
+ assert hasValues();
return valuesSize;
}
}
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 7bc7e56..edd774e 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
@@ -478,11 +478,18 @@
DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
FieldAccessInfoCollectionModifier.builder();
+
EnumUnboxingUtilityClasses utilityClasses =
EnumUnboxingUtilityClasses.builder(appView)
.synthesizeEnumUnboxingUtilityClasses(
- enumClassesToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder)
+ enumClassesToUnbox,
+ enumDataMap,
+ appBuilder,
+ fieldAccessInfoCollectionModifierBuilder)
.build();
+ utilityClasses.forEach(
+ utilityClass -> utilityClass.getDefinition().forEachProgramMethod(postBuilder::add));
+
fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, utilityClasses);
EnumUnboxingLens enumUnboxingLens =
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 9b159f2..3bbd636 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
@@ -4,10 +4,7 @@
package com.android.tools.r8.ir.optimize.enums;
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-
import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -47,12 +44,14 @@
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
@@ -70,6 +69,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
+ private final InternalOptions options;
private final EnumDataMap unboxedEnumsData;
private EnumUnboxingLens enumUnboxingLens;
private final EnumUnboxingUtilityClasses utilityClasses;
@@ -79,7 +79,6 @@
private final DexMethod ordinalUtilityMethod;
private final DexMethod equalsUtilityMethod;
private final DexMethod compareToUtilityMethod;
- private final DexMethod valuesUtilityMethod;
private final DexMethod zeroCheckMethod;
private final DexMethod zeroCheckMessageMethod;
@@ -89,12 +88,12 @@
EnumUnboxingUtilityClasses utilityClasses) {
this.appView = appView;
this.factory = appView.dexItemFactory();
+ this.options = appView.options();
this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
this.utilityClasses = utilityClasses;
// Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
- DexType sharedEnumUnboxingUtilityType =
- utilityClasses.getSharedEnumUnboxingUtilityClass().getType();
+ DexType sharedEnumUnboxingUtilityType = utilityClasses.getSharedUtilityClass().getType();
this.ordinalUtilityMethod =
factory.createMethod(
sharedEnumUnboxingUtilityType,
@@ -110,12 +109,6 @@
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(
- 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(
@@ -129,6 +122,14 @@
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
}
+ private LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
+ return utilityClasses.getLocalUtilityClass(enumType);
+ }
+
+ private SharedEnumUnboxingUtilityClass getSharedUtilityClass() {
+ return utilityClasses.getSharedUtilityClass();
+ }
+
public void setEnumUnboxingLens(EnumUnboxingLens enumUnboxingLens) {
this.enumUnboxingLens = enumUnboxingLens;
}
@@ -144,13 +145,21 @@
Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
+ Set<BasicBlock> seenBlocks = Sets.newIdentityHashSet();
+ Set<Instruction> instructionsToRemove = Sets.newIdentityHashSet();
Value zeroConstValue = null;
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
+ seenBlocks.add(block);
zeroConstValue = fixNullsInBlockPhis(code, block, zeroConstValue);
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
+ if (instructionsToRemove.contains(instruction)) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+
// Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
// counterpart. The rewriting (== or match) is based on the following:
// - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
@@ -302,38 +311,46 @@
StaticGet staticGet = instruction.asStaticGet();
DexField field = staticGet.getField();
DexType holder = field.holder;
- if (unboxedEnumsData.isUnboxedEnum(holder)) {
- if (staticGet.outValue() == null) {
- iterator.removeOrReplaceByDebugLocalRead();
- continue;
- }
- affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
- if (unboxedEnumsData.matchesValuesField(field)) {
- utilityMethods.computeIfAbsent(
- valuesUtilityMethod, m -> synthesizeValuesUtilityMethod());
- DexField fieldValues = createValuesField(holder);
- DexMethod methodValues = createValuesMethod(holder);
- utilityMethods.computeIfAbsent(
- methodValues,
- m ->
- computeValuesEncodedMethod(
- m, fieldValues, unboxedEnumsData.getValuesSize(holder)));
- Value rewrittenOutValue =
- code.createValue(
- ArrayTypeElement.create(TypeElement.getInt(), definitelyNotNull()));
- InvokeStatic invoke =
- new InvokeStatic(methodValues, rewrittenOutValue, ImmutableList.of());
- iterator.replaceCurrentInstruction(invoke);
- convertedEnums.put(invoke, holder);
- } else {
- // Replace by ordinal + 1 for null check (null is 0).
- assert unboxedEnumsData.hasUnboxedValueFor(field)
- : "Invalid read to " + field.name + ", error during enum analysis";
- ConstNumber intConstant =
- code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
- iterator.replaceCurrentInstruction(intConstant);
- convertedEnums.put(intConstant, holder);
- }
+ if (!unboxedEnumsData.isUnboxedEnum(holder)) {
+ continue;
+ }
+ if (staticGet.hasUnusedOutValue()) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+ affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+ if (unboxedEnumsData.matchesValuesField(field)) {
+ // Load the size of this enum's $VALUES array before the current instruction.
+ iterator.previous();
+ Value sizeValue =
+ iterator.insertConstIntInstruction(
+ code, options, unboxedEnumsData.getValuesSize(holder));
+ iterator.next();
+
+ // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
+ InvokeStatic invoke =
+ InvokeStatic.builder()
+ .setMethod(getSharedUtilityClass().getValuesMethod())
+ .setFreshOutValue(appView, code)
+ .setSingleArgument(sizeValue)
+ .build();
+ iterator.replaceCurrentInstruction(invoke);
+
+ convertedEnums.put(invoke, holder);
+
+ // Check if the call to SharedUtilityClass.values(size) is followed by a call to
+ // clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
+ // array. This is needed because the javac generated implementation of MyEnum.values()
+ // is implemented as `return $VALUES.clone()`.
+ removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
+ } else {
+ // Replace by ordinal + 1 for null check (null is 0).
+ assert unboxedEnumsData.hasUnboxedValueFor(field)
+ : "Invalid read to " + field.name + ", error during enum analysis";
+ ConstNumber intConstant =
+ code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
+ iterator.replaceCurrentInstruction(intConstant);
+ convertedEnums.put(intConstant, holder);
}
}
@@ -376,6 +393,26 @@
return affectedPhis;
}
+ private void removeRedundantValuesArrayCloning(
+ InvokeStatic invoke, Set<Instruction> instructionsToRemove, Set<BasicBlock> seenBlocks) {
+ for (Instruction user : invoke.outValue().aliasedUsers()) {
+ if (user.isInvokeVirtual()) {
+ InvokeVirtual cloneCandidate = user.asInvokeVirtual();
+ if (cloneCandidate.getInvokedMethod().match(appView.dexItemFactory().objectMembers.clone)) {
+ if (cloneCandidate.hasOutValue()) {
+ cloneCandidate.outValue().replaceUsers(invoke.outValue());
+ }
+ BasicBlock cloneBlock = cloneCandidate.getBlock();
+ if (cloneBlock == invoke.getBlock() || !seenBlocks.contains(cloneBlock)) {
+ instructionsToRemove.add(cloneCandidate);
+ } else {
+ cloneBlock.removeInstruction(cloneCandidate);
+ }
+ }
+ }
+ }
+ }
+
private void rewriteNameMethod(
InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
DexMethod toStringMethod =
@@ -406,7 +443,7 @@
while (iterator.hasNext() && iterator.peekNext().isArgument()) {
iterator.next();
}
- return iterator.insertConstNumberInstruction(code, appView.options(), 0, TypeElement.getInt());
+ return iterator.insertConstIntInstruction(code, options, 0);
}
private DexMethod computeInstanceFieldMethod(DexField field) {
@@ -465,35 +502,6 @@
return type.toSourceString().replace('.', '$');
}
- private DexField createValuesField(DexType enumType) {
- return createValuesField(
- enumType, utilityClasses.getLocalEnumUnboxingUtilityClass(enumType), factory);
- }
-
- static DexField createValuesField(
- DexType enumType, DexType enumUtilityClass, DexItemFactory dexItemFactory) {
- return dexItemFactory.createField(
- enumUtilityClass,
- dexItemFactory.intArrayType,
- "$$values$$field$" + compatibleName(enumType));
- }
-
- private DexMethod createValuesMethod(DexType enumType) {
- return factory.createMethod(
- utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
- factory.createProto(factory.intArrayType),
- "$$values$$method$" + compatibleName(enumType));
- }
-
- private DexEncodedMethod computeValuesEncodedMethod(
- DexMethod method, DexField fieldValues, int numEnumInstances) {
- CfCode cfCode =
- new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
- appView, method.holder, fieldValues, numEnumInstances, valuesUtilityMethod)
- .generateCfCode();
- return synthesizeUtilityMethod(cfCode, method, true);
- }
-
private DexMethod computeInstanceFieldUtilityMethod(DexType enumType, DexField field) {
assert unboxedEnumsData.isUnboxedEnum(enumType);
assert field.holder == enumType || field.holder == factory.enumType;
@@ -505,7 +513,7 @@
+ compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
- utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
+ utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(field.type, factory.intType),
methodName);
utilityMethods.computeIfAbsent(
@@ -519,7 +527,7 @@
String methodName = "string$valueOf$" + compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
- utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
+ utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(factory.stringType, factory.intType),
methodName);
AbstractValue nullString =
@@ -534,7 +542,7 @@
assert unboxedEnumsData.isUnboxedEnum(enumType);
DexMethod valueOf =
factory.createMethod(
- utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
+ utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(factory.intType, factory.stringType),
"valueOf" + compatibleName(enumType));
utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
@@ -594,7 +602,7 @@
}
// We make the order deterministic.
for (List<T> value : encodedMembersMap.values()) {
- value.sort((m1, m2) -> m1.getReference().compareTo(m2.getReference()));
+ value.sort(Comparator.comparing(DexEncodedMember::getReference));
}
return encodedMembersMap;
}
@@ -611,7 +619,7 @@
unboxedEnumsData.getInstanceFieldData(enumType, field).asEnumFieldMappingData(),
nullValue)
.generateCfCode();
- return synthesizeUtilityMethod(cfCode, method, false);
+ return synthesizeUtilityMethod(cfCode, method);
}
private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
@@ -628,51 +636,45 @@
.getInstanceFieldData(enumType, factory.enumMembers.nameField)
.asEnumFieldMappingData())
.generateCfCode();
- return synthesizeUtilityMethod(cfCode, method, false);
+ return synthesizeUtilityMethod(cfCode, method);
}
private DexEncodedMethod synthesizeZeroCheckMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
- return synthesizeUtilityMethod(cfCode, zeroCheckMethod, false);
+ return synthesizeUtilityMethod(cfCode, zeroCheckMethod);
}
private DexEncodedMethod synthesizeZeroCheckMessageMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(
appView.options(), zeroCheckMessageMethod);
- return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod, false);
+ return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod);
}
private DexEncodedMethod synthesizeOrdinalMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
- return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod, false);
+ return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod);
}
private DexEncodedMethod synthesizeEqualsMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
- return synthesizeUtilityMethod(cfCode, equalsUtilityMethod, false);
+ return synthesizeUtilityMethod(cfCode, equalsUtilityMethod);
}
private DexEncodedMethod synthesizeCompareToMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
appView.options(), compareToUtilityMethod);
- return synthesizeUtilityMethod(cfCode, compareToUtilityMethod, false);
+ return synthesizeUtilityMethod(cfCode, compareToUtilityMethod);
}
- private DexEncodedMethod synthesizeValuesUtilityMethod() {
- CfCode cfCode =
- EnumUnboxingCfMethods.EnumUnboxingMethods_values(appView.options(), valuesUtilityMethod);
- return synthesizeUtilityMethod(cfCode, valuesUtilityMethod, false);
- }
-
- private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method, boolean sync) {
+ private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method) {
return new DexEncodedMethod(
method,
- synthesizedMethodAccessFlags(sync),
+ MethodAccessFlags.createPublicStaticSynthetic(),
MethodTypeSignature.noSignature(),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
@@ -680,12 +682,4 @@
true,
REQUIRED_CLASS_FILE_VERSION);
}
-
- private MethodAccessFlags synthesizedMethodAccessFlags(boolean sync) {
- int access = Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC;
- if (sync) {
- access = access | Constants.ACC_SYNCHRONIZED;
- }
- return MethodAccessFlags.fromSharedAccessFlags(access, false);
- }
}
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 d36d9b7..8bab979 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
@@ -67,7 +67,7 @@
if (m.isInitializer()) {
clearEnumToUnboxMethod(m);
} else {
- DexType newHolder = utilityClasses.getLocalEnumUnboxingUtilityClass(clazz);
+ DexType newHolder = utilityClasses.getLocalUtilityClass(clazz).getType();
List<DexEncodedMethod> movedMethods =
unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
@@ -178,7 +178,7 @@
newMethod =
factory.createInstanceInitializerWithFreshProto(
newMethod,
- utilityClasses.getSharedEnumUnboxingUtilityClass().getType(),
+ utilityClasses.getSharedUtilityClass().getType(),
tryMethod -> holder.lookupMethod(tryMethod) == null);
} else {
int index = 0;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
new file mode 100644
index 0000000..6026c71
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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 com.android.tools.r8.graph.DexProgramClass;
+
+public abstract class EnumUnboxingUtilityClass {
+
+ public abstract DexProgramClass getDefinition();
+}
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
index dda4779..1753375 100644
--- 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
@@ -4,63 +4,51 @@
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.DexItemFactory;
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;
+import java.util.function.Consumer;
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;
+ private final ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
// 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 final SharedEnumUnboxingUtilityClass sharedUtilityClass;
private EnumUnboxingUtilityClasses(
- DexProgramClass sharedEnumUnboxingUtilityClass,
- ImmutableMap<DexType, DexProgramClass> localEnumUnboxingUtilityClasses) {
- this.sharedEnumUnboxingUtilityClass = sharedEnumUnboxingUtilityClass;
- this.localEnumUnboxingUtilityClasses = localEnumUnboxingUtilityClasses;
+ SharedEnumUnboxingUtilityClass sharedUtilityClass,
+ ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses) {
+ this.sharedUtilityClass = sharedUtilityClass;
+ this.localUtilityClasses = localUtilityClasses;
}
- public DexType getLocalEnumUnboxingUtilityClass(DexProgramClass enumClass) {
- return getLocalEnumUnboxingUtilityClass(enumClass.getType());
+ public void forEach(Consumer<? super EnumUnboxingUtilityClass> consumer) {
+ localUtilityClasses.values().forEach(consumer);
+ consumer.accept(getSharedUtilityClass());
}
- public DexType getLocalEnumUnboxingUtilityClass(DexType enumType) {
- DexProgramClass localEnumUnboxingUtilityClass = localEnumUnboxingUtilityClasses.get(enumType);
+ public LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexProgramClass enumClass) {
+ return getLocalUtilityClass(enumClass.getType());
+ }
+
+ public LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
+ LocalEnumUnboxingUtilityClass localEnumUnboxingUtilityClass = localUtilityClasses.get(enumType);
assert localEnumUnboxingUtilityClass != null;
- return localEnumUnboxingUtilityClass.getType();
+ return localEnumUnboxingUtilityClass;
}
- public DexProgramClass getSharedEnumUnboxingUtilityClass() {
- return sharedEnumUnboxingUtilityClass;
+ public SharedEnumUnboxingUtilityClass getSharedUtilityClass() {
+ return sharedUtilityClass;
}
public static Builder builder(AppView<AppInfoWithLiveness> appView) {
@@ -69,10 +57,9 @@
public static class Builder {
- private final AppView<?> appView;
- private final Map<DexType, DexProgramClass> localEnumUnboxingUtilityClasses =
- new IdentityHashMap<>();
- private DexProgramClass sharedEnumUnboxingUtilityClass;
+ private final AppView<AppInfoWithLiveness> appView;
+ private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
+ private SharedEnumUnboxingUtilityClass sharedUtilityClass;
public Builder(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
@@ -80,130 +67,42 @@
public Builder synthesizeEnumUnboxingUtilityClasses(
Set<DexProgramClass> enumsToUnbox,
+ EnumDataMap enumDataMap,
DirectMappedDexApplication.Builder appBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- synthesizeLocalUtilityClasses(
- enumsToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder);
- synthesizeSharedUtilityClass(enumsToUnbox, appBuilder);
+ SharedEnumUnboxingUtilityClass sharedUtilityClass =
+ SharedEnumUnboxingUtilityClass.builder(
+ appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder)
+ .build(appBuilder);
+ ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
+ createLocalUtilityClasses(enumsToUnbox, appBuilder);
+ this.localUtilityClasses = localUtilityClasses;
+ this.sharedUtilityClass = sharedUtilityClass;
return this;
}
public EnumUnboxingUtilityClasses build() {
- return new EnumUnboxingUtilityClasses(
- sharedEnumUnboxingUtilityClass, ImmutableMap.copyOf(localEnumUnboxingUtilityClasses));
+ return new EnumUnboxingUtilityClasses(sharedUtilityClass, localUtilityClasses);
}
- 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(
+ private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> createLocalUtilityClasses(
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;
- }
+ ImmutableMap.Builder<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
+ ImmutableMap.builder();
+ for (DexProgramClass enumToUnbox : enumsToUnbox) {
+ localUtilityClasses.put(
+ enumToUnbox.getType(),
+ LocalEnumUnboxingUtilityClass.builder(appView, enumToUnbox).build(appBuilder));
}
- return deterministicContext;
+ return localUtilityClasses.build();
}
- 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));
+ static DexType getUtilityClassType(
+ DexProgramClass context, String suffix, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createType(
+ DescriptorUtils.getDescriptorFromClassBinaryName(
+ DescriptorUtils.getBinaryNameFromDescriptor(context.getType().toDescriptorString())
+ + suffix));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
new file mode 100644
index 0000000..f4d4636
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -0,0 +1,94 @@
+// 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 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.DexItemFactory;
+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.GenericSignature.ClassSignature;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
+
+public class LocalEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
+
+ private static final String ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX =
+ "$r8$EnumUnboxingLocalUtility";
+
+ private final DexProgramClass localUtilityClass;
+
+ public LocalEnumUnboxingUtilityClass(DexProgramClass localUtilityClass) {
+ this.localUtilityClass = localUtilityClass;
+ }
+
+ public static Builder builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+ return new Builder(appView, enumToUnbox);
+ }
+
+ @Override
+ public DexProgramClass getDefinition() {
+ return localUtilityClass;
+ }
+
+ public DexType getType() {
+ return localUtilityClass.getType();
+ }
+
+ public static class Builder {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
+ private final DexProgramClass enumToUnbox;
+ private final DexType localUtilityClassType;
+
+ private Builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.enumToUnbox = enumToUnbox;
+ this.localUtilityClassType =
+ EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
+ enumToUnbox, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX, dexItemFactory);
+
+ assert appView.appInfo().definitionForWithoutExistenceAssert(localUtilityClassType) == null;
+ }
+
+ LocalEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+ DexProgramClass clazz = createClass();
+ appBuilder.addSynthesizedClass(clazz);
+ appView.appInfo().addSynthesizedClass(clazz, enumToUnbox);
+ return new LocalEnumUnboxingUtilityClass(clazz);
+ }
+
+ private DexProgramClass createClass() {
+ return 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(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ appView.dexItemFactory().getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
new file mode 100644
index 0000000..19adc9f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -0,0 +1,279 @@
+// 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 com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.DexItemFactory;
+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.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.objectweb.asm.Opcodes;
+
+public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
+
+ public static final String ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX =
+ "$r8$EnumUnboxingSharedUtility";
+
+ private final DexProgramClass sharedUtilityClass;
+ private final ProgramField valuesField;
+ private final ProgramMethod valuesMethod;
+
+ public SharedEnumUnboxingUtilityClass(
+ DexProgramClass sharedUtilityClass, ProgramField valuesField, ProgramMethod valuesMethod) {
+ this.sharedUtilityClass = sharedUtilityClass;
+ this.valuesField = valuesField;
+ this.valuesMethod = valuesMethod;
+ }
+
+ public static Builder builder(
+ AppView<AppInfoWithLiveness> appView,
+ EnumDataMap enumDataMap,
+ Set<DexProgramClass> enumsToUnbox,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ return new Builder(
+ appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
+ }
+
+ @Override
+ public DexProgramClass getDefinition() {
+ return sharedUtilityClass;
+ }
+
+ public ProgramField getValuesField() {
+ return valuesField;
+ }
+
+ public ProgramMethod getValuesMethod() {
+ return valuesMethod;
+ }
+
+ public DexType getType() {
+ return sharedUtilityClass.getType();
+ }
+
+ public static class Builder {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
+ private final EnumDataMap enumDataMap;
+ private final Set<DexProgramClass> enumsToUnbox;
+ private final FieldAccessInfoCollectionModifier.Builder
+ fieldAccessInfoCollectionModifierBuilder;
+ private final DexType sharedUtilityClassType;
+
+ private DexEncodedField valuesField;
+ private DexEncodedMethod valuesMethod;
+
+ private Builder(
+ AppView<AppInfoWithLiveness> appView,
+ EnumDataMap enumDataMap,
+ Set<DexProgramClass> enumsToUnbox,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.enumDataMap = enumDataMap;
+ this.enumsToUnbox = enumsToUnbox;
+ this.fieldAccessInfoCollectionModifierBuilder = fieldAccessInfoCollectionModifierBuilder;
+ this.sharedUtilityClassType =
+ EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
+ findDeterministicContextType(enumsToUnbox),
+ ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX,
+ dexItemFactory);
+
+ assert appView.appInfo().definitionForWithoutExistenceAssert(sharedUtilityClassType) == null;
+ }
+
+ SharedEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+ DexProgramClass clazz = createClass();
+ appBuilder.addSynthesizedClass(clazz);
+ appView.appInfo().addSynthesizedClassToBase(clazz, enumsToUnbox);
+ return new SharedEnumUnboxingUtilityClass(
+ clazz, new ProgramField(clazz, valuesField), new ProgramMethod(clazz, valuesMethod));
+ }
+
+ private DexProgramClass createClass() {
+ DexEncodedField valuesField = createValuesField(sharedUtilityClassType);
+ return new DexProgramClass(
+ sharedUtilityClassType,
+ null,
+ new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+ ClassAccessFlags.createPublicFinalSynthetic(),
+ dexItemFactory.objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ ClassSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ new DexEncodedField[] {valuesField},
+ DexEncodedField.EMPTY_ARRAY,
+ new DexEncodedMethod[] {
+ createClassInitializer(valuesField), createValuesMethod(valuesField)
+ },
+ DexEncodedMethod.EMPTY_ARRAY,
+ dexItemFactory.getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ }
+
+ // Fields.
+
+ private DexEncodedField createValuesField(DexType sharedUtilityClassType) {
+ DexEncodedField valuesField =
+ new DexEncodedField(
+ dexItemFactory.createField(
+ sharedUtilityClassType, dexItemFactory.intArrayType, "$VALUES"),
+ FieldAccessFlags.createPublicStaticFinalSynthetic(),
+ FieldTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.NO_STATIC_VALUE,
+ DexEncodedField.NOT_DEPRECATED,
+ DexEncodedField.D8_R8_SYNTHESIZED);
+ fieldAccessInfoCollectionModifierBuilder
+ .recordFieldReadInUnknownContext(valuesField.getReference())
+ .recordFieldWriteInUnknownContext(valuesField.getReference());
+ this.valuesField = valuesField;
+ return valuesField;
+ }
+
+ // Methods.
+
+ private DexEncodedMethod createClassInitializer(DexEncodedField valuesField) {
+ return new DexEncodedMethod(
+ dexItemFactory.createClassInitializer(sharedUtilityClassType),
+ MethodAccessFlags.createForClassInitializer(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ createClassInitializerCode(valuesField),
+ DexEncodedMethod.D8_R8_SYNTHESIZED,
+ CfVersion.V1_6);
+ }
+
+ private CfCode createClassInitializerCode(DexEncodedField valuesField) {
+ int maxValuesArraySize = enumDataMap.getMaxValuesSize();
+ int numberOfInstructions = 4 + maxValuesArraySize * 4;
+ List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
+ instructions.add(new CfConstNumber(maxValuesArraySize, ValueType.INT));
+ instructions.add(new CfNewArray(dexItemFactory.intArrayType));
+ for (int i = 0; i < maxValuesArraySize; i++) {
+ instructions.add(new CfStackInstruction(Opcode.Dup));
+ instructions.add(new CfConstNumber(i, ValueType.INT));
+ // i + 1 because 0 represents the null value.
+ instructions.add(new CfConstNumber(i + 1, ValueType.INT));
+ instructions.add(new CfArrayStore(MemberType.INT));
+ }
+ instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, valuesField.getReference()));
+ instructions.add(new CfReturnVoid());
+
+ int maxStack = 4;
+ int maxLocals = 0;
+ return new CfCode(
+ sharedUtilityClassType,
+ maxStack,
+ maxLocals,
+ instructions,
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ private DexEncodedMethod createValuesMethod(DexEncodedField valuesField) {
+ DexEncodedMethod valuesMethod =
+ new DexEncodedMethod(
+ dexItemFactory.createMethod(
+ sharedUtilityClassType,
+ dexItemFactory.createProto(dexItemFactory.intArrayType, dexItemFactory.intType),
+ "values"),
+ MethodAccessFlags.createPublicStaticSynthetic(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ createValuesMethodCode(valuesField),
+ DexEncodedMethod.D8_R8_SYNTHESIZED,
+ CfVersion.V1_6);
+ this.valuesMethod = valuesMethod;
+ return valuesMethod;
+ }
+
+ private CfCode createValuesMethodCode(DexEncodedField valuesField) {
+ int maxStack = 5;
+ int maxLocals = 2;
+ int argumentLocalSlot = 0;
+ int resultLocalSlot = 1;
+ return new CfCode(
+ sharedUtilityClassType,
+ maxStack,
+ maxLocals,
+ ImmutableList.of(
+ // int[] result = new int[size];
+ new CfLoad(ValueType.INT, argumentLocalSlot),
+ new CfNewArray(dexItemFactory.intArrayType),
+ new CfStore(ValueType.OBJECT, resultLocalSlot),
+ // System.arraycopy(SharedUtilityClass.$VALUES, 0, result, 0, size);
+ new CfFieldInstruction(Opcodes.GETSTATIC, valuesField.getReference()),
+ new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.OBJECT, resultLocalSlot),
+ new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.INT, argumentLocalSlot),
+ new CfInvoke(
+ Opcodes.INVOKESTATIC, dexItemFactory.javaLangSystemMethods.arraycopy, false),
+ // return result
+ new CfLoad(ValueType.OBJECT, resultLocalSlot),
+ new CfReturn(ValueType.OBJECT)),
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ private static 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/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index faf1061..e7b5dce 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingUtilityClasses;
+import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -90,7 +90,8 @@
c ->
c.getOriginalName()
.contains(
- EnumUnboxingUtilityClasses.ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
+ SharedEnumUnboxingUtilityClass
+ .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 58d220f..4ef092d 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.EnumUnboxingUtilityClasses;
+import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -95,7 +95,8 @@
c ->
c.getOriginalName()
.contains(
- EnumUnboxingUtilityClasses.ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
+ SharedEnumUnboxingUtilityClass
+ .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
}
static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java
new file mode 100644
index 0000000..82c48eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2021, 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 static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantValuesCloneEnumUnboxingTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // Verify that there are no calls to clone().
+ .inspect(
+ inspector ->
+ inspector.forAllClasses(
+ clazz ->
+ clazz.forAllMethods(
+ method -> assertThat(method, not(invokesMethodWithName("clone"))))))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ for (MyEnum e : MyEnum.values()) {
+ System.out.println(e.name());
+ }
+ }
+ }
+
+ enum MyEnum {
+ A
+ }
+}