Move enum optimizations to their own package
- Extract EnumValueOptimizations from
SwitchUtils and CodeRewriter
- Move EnumUnboxing and EnumValue
optimizations to their own package
- Enable EnumValueInfoCollector if any
of EnumUnboxing or Enumvalue optimizations
are enabled
Bug: 147860220
Change-Id: I7001886ac5cd6c3f263df9b0828c7016ba21095a
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ecf6749..568e6ca 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -35,7 +35,6 @@
import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
-import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.ir.optimize.NestReducer;
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
@@ -43,6 +42,7 @@
import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
+import com.android.tools.r8.ir.optimize.enums.EnumInfoMapCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.kotlin.Kotlin;
@@ -520,6 +520,8 @@
// Collect switch maps and ordinals maps.
if (options.enableEnumValueOptimization) {
appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness).run());
+ }
+ if (options.enableEnumValueOptimization || options.enableEnumUnboxing) {
appViewWithLiveness.setAppInfo(new EnumInfoMapCollector(appViewWithLiveness).run());
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 37a8dc6..717df30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -21,9 +21,9 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
import com.android.tools.r8.utils.PredicateSet;
@@ -91,7 +91,7 @@
public void inlineCallsToDynamicMethod(
DexEncodedMethod method,
IRCode code,
- CodeRewriter codeRewriter,
+ EnumValueOptimizer enumValueOptimizer,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
Inliner inliner) {
@@ -103,8 +103,8 @@
// Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to
// get rid of the enum switch in dynamicMethod().
- if (appView.options().enableEnumValueOptimization) {
- codeRewriter.rewriteConstantEnumMethodCalls(code);
+ if (enumValueOptimizer != null) {
+ enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 5c76d71..dce9801 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -59,7 +59,6 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.Devirtualizer;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
-import com.android.tools.r8.ir.optimize.EnumUnboxer;
import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -72,6 +71,8 @@
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -158,6 +159,7 @@
private final TypeChecker typeChecker;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
private final ServiceLoaderRewriter serviceLoaderRewriter;
+ private final EnumValueOptimizer enumValueOptimizer;
private final EnumUnboxer enumUnboxer;
// Assumers that will insert Assume instructions.
@@ -247,6 +249,7 @@
this.stringSwitchRemover = null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumValueOptimizer = null;
this.enumUnboxer = null;
return;
}
@@ -326,6 +329,8 @@
? new DesugaredLibraryAPIConverter(
appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED)
: null;
+ this.enumValueOptimizer =
+ options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
} else {
this.classInliner = null;
@@ -350,6 +355,7 @@
: null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumValueOptimizer = null;
this.enumUnboxer = null;
}
this.stringSwitchRemover =
@@ -1174,10 +1180,10 @@
timing.end();
}
- if (options.enableEnumValueOptimization) {
+ if (enumValueOptimizer != null) {
assert appView.enableWholeProgramOptimizations();
timing.begin("Remove switch maps");
- codeRewriter.removeSwitchMaps(code);
+ enumValueOptimizer.removeSwitchMaps(code);
timing.end();
}
@@ -1251,10 +1257,10 @@
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
timing.end();
- if (options.enableEnumValueOptimization) {
+ if (enumValueOptimizer != null) {
assert appView.enableWholeProgramOptimizations();
timing.begin("Rewrite constant enum methods");
- codeRewriter.rewriteConstantEnumMethodCalls(code);
+ enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
timing.end();
}
@@ -1365,6 +1371,7 @@
appView.withLiveness(),
codeRewriter,
stringOptimizer,
+ enumValueOptimizer,
method,
code,
feedback,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5b695a7..7f2cee9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
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.DexItemFactory.ThrowableMethods;
import com.android.tools.r8.graph.DexMethod;
@@ -57,11 +56,9 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -76,9 +73,7 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LongInterval;
@@ -93,7 +88,6 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
-import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -1043,69 +1037,6 @@
}
/**
- * Inline the indirection of switch maps into the switch statement.
- * <p>
- * To ensure binary compatibility, javac generated code does not use ordinal values of enums
- * directly in switch statements but instead generates a companion class that computes a mapping
- * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can
- * analyze these maps and inline the indirection into the switch map again.
- * <p>
- * In particular, we look for code of the form
- *
- * <blockquote><pre>
- * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
- * ...
- * }
- * </pre></blockquote>
- */
- public void removeSwitchMaps(IRCode code) {
- for (BasicBlock block : code.blocks) {
- JumpInstruction exit = block.exit();
- // Pattern match a switch on a switch map as input.
- if (exit.isIntSwitch()) {
- IntSwitch switchInsn = exit.asIntSwitch();
- EnumSwitchInfo info = SwitchUtils.analyzeSwitchOverEnum(switchInsn, appView.withLiveness());
- if (info != null) {
- Int2IntMap targetMap = new Int2IntArrayMap();
- for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
- assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
- EnumValueInfo valueInfo =
- info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
- targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
- }
- int[] keys = targetMap.keySet().toIntArray();
- Arrays.sort(keys);
- int[] targets = new int[keys.length];
- for (int i = 0; i < keys.length; i++) {
- targets[i] = targetMap.get(keys[i]);
- }
-
- IntSwitch newSwitch =
- new IntSwitch(
- info.ordinalInvoke.outValue(),
- keys,
- targets,
- switchInsn.getFallthroughBlockIndex());
- // Replace the switch itself.
- exit.replace(newSwitch, code);
- // If the original input to the switch is now unused, remove it too. It is not dead
- // as it might have side-effects but we ignore these here.
- Instruction arrayGet = info.arrayGet;
- if (!arrayGet.outValue().hasUsers()) {
- arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
- arrayGet.getBlock().removeInstruction(arrayGet);
- }
- Instruction staticGet = info.staticGet;
- if (!staticGet.outValue().hasUsers()) {
- assert staticGet.inValues().isEmpty();
- staticGet.getBlock().removeInstruction(staticGet);
- }
- }
- }
- }
- }
-
- /**
* Rewrite all branch targets to the destination of trivial goto chains when possible. Does not
* rewrite fallthrough targets as that would require block reordering and the transformation only
* makes sense after SSA destruction where there are no phis.
@@ -2966,80 +2897,6 @@
return true;
}
- public void rewriteConstantEnumMethodCalls(IRCode code) {
- if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
- return;
- }
-
- InstructionListIterator iterator = code.instructionListIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
-
- if (!current.isInvokeMethodWithReceiver()) {
- continue;
- }
- InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
- DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
- boolean isOrdinalInvoke = invokedMethod == dexItemFactory.enumMethods.ordinal;
- boolean isNameInvoke = invokedMethod == dexItemFactory.enumMethods.name;
- boolean isToStringInvoke = invokedMethod == dexItemFactory.enumMethods.toString;
- if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
- continue;
- }
-
- Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
- if (receiver.isPhi()) {
- continue;
- }
- Instruction definition = receiver.getDefinition();
- if (!definition.isStaticGet()) {
- continue;
- }
- DexField enumField = definition.asStaticGet().getField();
-
- Map<DexField, EnumValueInfo> valueInfoMap =
- appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
- if (valueInfoMap == null) {
- continue;
- }
-
- // The receiver value is identified as being from a constant enum field lookup by the fact
- // that it is a static-get to a field whose type is the same as the enclosing class (which
- // is known to be an enum type). An enum may still define a static field using the enum type
- // so ensure the field is present in the ordinal map for final validation.
- EnumValueInfo valueInfo = valueInfoMap.get(enumField);
- if (valueInfo == null) {
- continue;
- }
-
- Value outValue = methodWithReceiver.outValue();
- if (isOrdinalInvoke) {
- iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
- } else if (isNameInvoke) {
- iterator.replaceCurrentInstruction(
- new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
- } else {
- assert isToStringInvoke;
- DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
- if (!enumClazz.accessFlags.isFinal()) {
- continue;
- }
- if (appView
- .appInfo()
- .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
- .getSingleTarget()
- .method
- != dexItemFactory.enumMethods.toString) {
- continue;
- }
- iterator.replaceCurrentInstruction(
- new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
- }
- }
-
- assert code.isConsistentSSA();
- }
-
public void rewriteKnownArrayLengthCalls(IRCode code) {
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
deleted file mode 100644
index 0dd0bcf..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2017, 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;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.ArrayGet;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import java.util.Map;
-
-public class SwitchUtils {
-
- public static final class EnumSwitchInfo {
- public final DexType enumClass;
- public final Instruction ordinalInvoke;
- public final Instruction arrayGet;
- public final Instruction staticGet;
- public final Int2ReferenceMap<DexField> indexMap;
- public final Map<DexField, EnumValueInfo> valueInfoMap;
-
- private EnumSwitchInfo(DexType enumClass,
- Instruction ordinalInvoke,
- Instruction arrayGet, Instruction staticGet,
- Int2ReferenceMap<DexField> indexMap,
- Map<DexField, EnumValueInfo> valueInfoMap) {
- this.enumClass = enumClass;
- this.ordinalInvoke = ordinalInvoke;
- this.arrayGet = arrayGet;
- this.staticGet = staticGet;
- this.indexMap = indexMap;
- this.valueInfoMap = valueInfoMap;
- }
- }
-
- /**
- * Looks for a switch statement over the enum companion class of the form
- *
- * <blockquote>
- *
- * <pre>
- * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
- * ...
- * }
- * </pre>
- *
- * </blockquote>
- *
- * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
- * and {@link SwitchMapCollector} for details.
- */
- public static EnumSwitchInfo analyzeSwitchOverEnum(
- Instruction switchInsn, AppView<AppInfoWithLiveness> appView) {
- AppInfoWithLiveness appInfo = appView.appInfo();
- Instruction input = switchInsn.inValues().get(0).definition;
- if (input == null || !input.isArrayGet()) {
- return null;
- }
- ArrayGet arrayGet = input.asArrayGet();
- Instruction index = arrayGet.index().definition;
- if (index == null || !index.isInvokeVirtual()) {
- return null;
- }
- InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
- DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
- DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
- DexItemFactory dexItemFactory = appInfo.dexItemFactory();
- // After member rebinding, enumClass will be the actual java.lang.Enum class.
- if (enumClass == null
- || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
- || ordinalMethod.name != dexItemFactory.ordinalMethodName
- || ordinalMethod.proto.returnType != dexItemFactory.intType
- || !ordinalMethod.proto.parameters.isEmpty()) {
- return null;
- }
- Instruction array = arrayGet.array().definition;
- if (array == null || !array.isStaticGet()) {
- return null;
- }
- StaticGet staticGet = array.asStaticGet();
- Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
- if (indexMap == null || indexMap.isEmpty()) {
- return null;
- }
- // Due to member rebinding, only the fields are certain to provide the actual enums
- // class.
- DexType enumType = indexMap.values().iterator().next().holder;
- Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
- if (valueInfoMap == null) {
- return null;
- }
- return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap,
- valueInfoMap);
- }
-
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index c4a761b..b20eba1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -164,6 +165,7 @@
AppView<AppInfoWithLiveness> appView,
CodeRewriter codeRewriter,
StringOptimizer stringOptimizer,
+ EnumValueOptimizer enumValueOptimizer,
DexEncodedMethod method,
IRCode code,
OptimizationFeedback feedback,
@@ -282,7 +284,7 @@
appView.withGeneratedMessageLiteBuilderShrinker(
shrinker ->
shrinker.inlineCallsToDynamicMethod(
- method, code, codeRewriter, feedback, methodProcessor, inliner));
+ method, code, enumValueOptimizer, feedback, methodProcessor, inliner));
}
if (anyInlinedMethods) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
index 2472589..e0991e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
@@ -1,7 +1,7 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
similarity index 99%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c38b7b0..5d737b5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -2,7 +2,7 @@
// 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;
+package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 3fb84e1..7b207c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -2,7 +2,7 @@
// 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;
+package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
@@ -10,7 +10,7 @@
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.ir.optimize.EnumUnboxer.Reason;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
import com.google.common.collect.Sets;
import java.util.Set;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
new file mode 100644
index 0000000..c8122470
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -0,0 +1,268 @@
+// 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.DexClass;
+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.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.SwitchMapCollector;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import java.util.Arrays;
+import java.util.Map;
+
+public class EnumValueOptimizer {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory factory;
+
+ public EnumValueOptimizer(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void rewriteConstantEnumMethodCalls(IRCode code) {
+ if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
+ return;
+ }
+
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+
+ if (!current.isInvokeMethodWithReceiver()) {
+ continue;
+ }
+ InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
+ DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
+ boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
+ boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
+ boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
+ if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
+ continue;
+ }
+
+ Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
+ if (receiver.isPhi()) {
+ continue;
+ }
+ Instruction definition = receiver.getDefinition();
+ if (!definition.isStaticGet()) {
+ continue;
+ }
+ DexField enumField = definition.asStaticGet().getField();
+
+ Map<DexField, EnumValueInfo> valueInfoMap =
+ appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
+ if (valueInfoMap == null) {
+ continue;
+ }
+
+ // The receiver value is identified as being from a constant enum field lookup by the fact
+ // that it is a static-get to a field whose type is the same as the enclosing class (which
+ // is known to be an enum type). An enum may still define a static field using the enum type
+ // so ensure the field is present in the ordinal map for final validation.
+ EnumValueInfo valueInfo = valueInfoMap.get(enumField);
+ if (valueInfo == null) {
+ continue;
+ }
+
+ Value outValue = methodWithReceiver.outValue();
+ if (isOrdinalInvoke) {
+ iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
+ } else if (isNameInvoke) {
+ iterator.replaceCurrentInstruction(
+ new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+ } else {
+ assert isToStringInvoke;
+ DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
+ if (!enumClazz.accessFlags.isFinal()) {
+ continue;
+ }
+ DexEncodedMethod singleTarget =
+ appView
+ .appInfo()
+ .resolveMethodOnClass(valueInfo.type, factory.objectMethods.toString)
+ .getSingleTarget();
+ if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
+ continue;
+ }
+ iterator.replaceCurrentInstruction(
+ new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+ }
+ }
+ assert code.isConsistentSSA();
+ }
+
+ /**
+ * Inline the indirection of switch maps into the switch statement.
+ *
+ * <p>To ensure binary compatibility, javac generated code does not use ordinal values of enums
+ * directly in switch statements but instead generates a companion class that computes a mapping
+ * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can analyze
+ * these maps and inline the indirection into the switch map again.
+ *
+ * <p>In particular, we look for code of the form
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+ * ...
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ */
+ public void removeSwitchMaps(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ JumpInstruction exit = block.exit();
+ // Pattern match a switch on a switch map as input.
+ if (!exit.isIntSwitch()) {
+ continue;
+ }
+ IntSwitch switchInsn = exit.asIntSwitch();
+ EnumSwitchInfo info = analyzeSwitchOverEnum(switchInsn);
+ if (info == null) {
+ continue;
+ }
+ Int2IntMap targetMap = new Int2IntArrayMap();
+ for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
+ assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
+ EnumValueInfo valueInfo = info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
+ targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
+ }
+ int[] keys = targetMap.keySet().toIntArray();
+ Arrays.sort(keys);
+ int[] targets = new int[keys.length];
+ for (int i = 0; i < keys.length; i++) {
+ targets[i] = targetMap.get(keys[i]);
+ }
+
+ IntSwitch newSwitch =
+ new IntSwitch(
+ info.ordinalInvoke.outValue(), keys, targets, switchInsn.getFallthroughBlockIndex());
+ // Replace the switch itself.
+ exit.replace(newSwitch, code);
+ // If the original input to the switch is now unused, remove it too. It is not dead
+ // as it might have side-effects but we ignore these here.
+ Instruction arrayGet = info.arrayGet;
+ if (!arrayGet.outValue().hasUsers()) {
+ arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
+ arrayGet.getBlock().removeInstruction(arrayGet);
+ }
+ Instruction staticGet = info.staticGet;
+ if (!staticGet.outValue().hasUsers()) {
+ assert staticGet.inValues().isEmpty();
+ staticGet.getBlock().removeInstruction(staticGet);
+ }
+ }
+ }
+
+ private static final class EnumSwitchInfo {
+
+ final DexType enumClass;
+ final Instruction ordinalInvoke;
+ final Instruction arrayGet;
+ public final Instruction staticGet;
+ final Int2ReferenceMap<DexField> indexMap;
+ final Map<DexField, EnumValueInfo> valueInfoMap;
+
+ private EnumSwitchInfo(
+ DexType enumClass,
+ Instruction ordinalInvoke,
+ Instruction arrayGet,
+ Instruction staticGet,
+ Int2ReferenceMap<DexField> indexMap,
+ Map<DexField, EnumValueInfo> valueInfoMap) {
+ this.enumClass = enumClass;
+ this.ordinalInvoke = ordinalInvoke;
+ this.arrayGet = arrayGet;
+ this.staticGet = staticGet;
+ this.indexMap = indexMap;
+ this.valueInfoMap = valueInfoMap;
+ }
+ }
+
+ /**
+ * Looks for a switch statement over the enum companion class of the form
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+ * ...
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ *
+ * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
+ * and {@link SwitchMapCollector} for details.
+ */
+ private EnumSwitchInfo analyzeSwitchOverEnum(Instruction switchInsn) {
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ Instruction input = switchInsn.inValues().get(0).definition;
+ if (input == null || !input.isArrayGet()) {
+ return null;
+ }
+ ArrayGet arrayGet = input.asArrayGet();
+ Instruction index = arrayGet.index().definition;
+ if (index == null || !index.isInvokeVirtual()) {
+ return null;
+ }
+ InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
+ DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
+ DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
+ DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+ // After member rebinding, enumClass will be the actual java.lang.Enum class.
+ if (enumClass == null
+ || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
+ || ordinalMethod.name != dexItemFactory.ordinalMethodName
+ || ordinalMethod.proto.returnType != dexItemFactory.intType
+ || !ordinalMethod.proto.parameters.isEmpty()) {
+ return null;
+ }
+ Instruction array = arrayGet.array().definition;
+ if (array == null || !array.isStaticGet()) {
+ return null;
+ }
+ StaticGet staticGet = array.asStaticGet();
+ Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
+ if (indexMap == null || indexMap.isEmpty()) {
+ return null;
+ }
+ // Due to member rebinding, only the fields are certain to provide the actual enums
+ // class.
+ DexType enumType = indexMap.values().iterator().next().holder;
+ Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
+ if (valueInfoMap == null) {
+ return null;
+ }
+ return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap, valueInfoMap);
+ }
+}