Optimize Class#isEnum()
- This reduces the code size of the enum comparison
in type switches
Change-Id: I129c72d9e3025f2013740c829b2b39cc98e17407
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 1dda2a0..5189d88 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1968,6 +1968,7 @@
public class ClassMethods {
public final DexMethod desiredAssertionStatus;
+ public final DexMethod isEnum = createMethod(classType, createProto(booleanType), "isEnum");
public final DexMethod forName;
public final DexMethod forName3;
public final DexMethod getClassLoader =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java
index b166b9e..91d8474 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java
@@ -5,41 +5,44 @@
package com.android.tools.r8.ir.optimize.library;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
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.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.AffectedValues;
-import com.android.tools.r8.utils.InternalOptions;
import java.util.Set;
public class ClassOptimizer extends StatelessLibraryMethodModelCollection {
- private final InternalOptions options;
- private final DexItemFactory dexItemFactory;
+ private final AppView<?> appView;
+ private final DexItemFactory factory;
private final DexMethod getConstructor;
private final DexMethod getDeclaredConstructor;
private final DexMethod getMethod;
private final DexMethod getDeclaredMethod;
+ private final DexMethod isEnum;
ClassOptimizer(AppView<?> appView) {
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- this.options = appView.options();
- this.dexItemFactory = dexItemFactory;
- getConstructor = dexItemFactory.classMethods.getConstructor;
- getDeclaredConstructor = dexItemFactory.classMethods.getDeclaredConstructor;
- getMethod = dexItemFactory.classMethods.getMethod;
- getDeclaredMethod = dexItemFactory.classMethods.getDeclaredMethod;
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ getConstructor = factory.classMethods.getConstructor;
+ getDeclaredConstructor = factory.classMethods.getDeclaredConstructor;
+ getMethod = factory.classMethods.getMethod;
+ getDeclaredMethod = factory.classMethods.getDeclaredMethod;
+ isEnum = factory.classMethods.isEnum;
}
@Override
public DexType getType() {
- return dexItemFactory.classType;
+ return factory.classType;
}
@Override
@@ -57,9 +60,30 @@
|| singleTargetReference.isIdenticalTo(getMethod)
|| singleTargetReference.isIdenticalTo(getDeclaredMethod)) {
EmptyVarargsUtil.replaceWithNullIfEmptyArray(
- invoke, invoke.arguments().size() - 1, code, instructionIterator, options);
+ invoke, invoke.arguments().size() - 1, code, instructionIterator, appView.options());
assert instructionIterator.peekPrevious() == invoke;
}
+ if (singleTargetReference.isIdenticalTo(isEnum)) {
+ Value receiver = invoke.getFirstArgument();
+ if (invoke.hasUnusedOutValue()) {
+ if (receiver.getType().isDefinitelyNotNull()) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ return instructionIterator;
+ }
+ Value aliasedReceiver = receiver.getAliasedValue();
+ if (aliasedReceiver.isConstClass()) {
+ ConstClass constClass = aliasedReceiver.getDefinition().asConstClass();
+ // An enum must both directly extend java.lang.Enum and have the ENUM bit set;
+ // classes for specialized enum constants don't do the former.
+ DexClass dexClass = appView.definitionFor(constClass.getType());
+ if (dexClass != null) {
+ boolean isEnumResult =
+ dexClass.isEnum() && dexClass.getSuperType().isIdenticalTo(factory.enumType);
+ instructionIterator.replaceCurrentInstructionWithConstBoolean(code, isEnumResult);
+ }
+ }
+ }
return instructionIterator;
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/IsEnumRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/enums/IsEnumRemovalTest.java
new file mode 100644
index 0000000..d0bab3a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/enums/IsEnumRemovalTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2025, 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.shaking.enums;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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 com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Set;
+import org.gradle.internal.impldep.org.testng.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IsEnumRemovalTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String EXPECTED = StringUtils.lines("true", "false", "false");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IsEnumRemovalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(IsEnumRemovalTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(IsEnumRemovalTest.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::inspectR8);
+ }
+
+ private void inspectR8(CodeInspector inspector) {
+ ClassSubject main = inspector.clazz(Main.class);
+ assertThat(main, isPresent());
+ Assert.assertEquals(
+ 0,
+ main.uniqueMethod()
+ .streamInstructions()
+ .filter(i -> i.isInvoke() && i.getMethod().getName().toString().equals("isEnum"))
+ .count());
+ }
+
+ public enum MyEnum {
+ A {
+ void foo() {
+ System.out.println("aa");
+ }
+ },
+ B {
+ void foo() {
+ System.out.println("bb");
+ }
+ };
+
+ abstract void foo();
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(MyEnum.class.isEnum());
+ System.out.println(MyEnum.B.getClass().isEnum());
+ System.out.println(Set.class.isEnum());
+ }
+ }
+}