Disable canonicalization for singleton fields

Bug: 171136616
Change-Id: I42a6046030ae104daf1f4b8a41aaf50b74904889
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 36fba35..912a030 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -11,6 +11,9 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
@@ -147,13 +150,19 @@
           continue;
         }
         SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
+        DexType fieldHolderType = singleFieldValue.getField().getHolderType();
         if (context.getDefinition().isClassInitializer()
-            && context.getHolderType() == singleFieldValue.getField().holder) {
+            && context.getHolderType() == fieldHolderType) {
           // Avoid that canonicalization inserts a read before the unique write in the class
           // initializer, as that would change the program behavior.
           continue;
         }
-        if (current.instructionMayHaveSideEffects(appView, context)) {
+        DexClass fieldHolder = appView.definitionFor(fieldHolderType);
+        DexEncodedField field = singleFieldValue.getField().lookupOnClass(fieldHolder);
+        if (field == null
+            || !field.isEnum()
+            || current.instructionMayHaveSideEffects(appView, context)) {
+          // Only allow canonicalization of enums.
           continue;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 54976af..97f9d07 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -8,10 +8,11 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -148,19 +149,24 @@
     }
   }
 
-  public boolean isFinal(DexEncodedField field) {
-    if (field.isProgramField(appView)) {
-      return field.isFinal();
+  public boolean isFinal(DexClassAndField field) {
+    if (field.isProgramField()) {
+      // Treat this field as being final if it is declared final or we have determined a constant
+      // value for it.
+      return field.getDefinition().isFinal()
+          || field.getDefinition().getOptimizationInfo().getAbstractValue().isSingleValue();
     }
-    return appView.libraryMethodOptimizer().isFinalLibraryField(field);
+    return appView.libraryMethodOptimizer().isFinalLibraryField(field.getDefinition());
   }
 
-  private DexEncodedField resolveField(DexField field) {
+  private DexClassAndField resolveField(DexField field) {
     if (appView.enableWholeProgramOptimizations()) {
-      return appView.appInfo().withLiveness().resolveField(field).getResolvedField();
+      SuccessfulFieldResolutionResult resolutionResult =
+          appView.appInfo().withLiveness().resolveField(field).asSuccessfulResolution();
+      return resolutionResult != null ? resolutionResult.getResolutionPair() : null;
     }
-    if (field.holder == method.getHolderType()) {
-      return method.getHolder().lookupField(field);
+    if (field.getHolderType() == method.getHolderType()) {
+      return method.getHolder().lookupProgramField(field);
     }
     return null;
   }
@@ -187,9 +193,9 @@
         while (it.hasNext()) {
           Instruction instruction = it.next();
           if (instruction.isFieldInstruction()) {
-            DexField field = instruction.asFieldInstruction().getField();
-            DexEncodedField definition = resolveField(field);
-            if (definition == null || definition.isVolatile()) {
+            DexField reference = instruction.asFieldInstruction().getField();
+            DexClassAndField field = resolveField(reference);
+            if (field == null || field.getDefinition().isVolatile()) {
               killAllNonFinalActiveFields();
               continue;
             }
@@ -200,7 +206,7 @@
                 continue;
               }
               Value object = instanceGet.object().getAliasedValue();
-              FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+              FieldAndObject fieldAndObject = new FieldAndObject(reference, object);
               FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
               if (replacement != null) {
                 replacement.eliminateRedundantRead(it, instanceGet);
@@ -215,10 +221,11 @@
               killNonFinalActiveFields(instancePut);
               // ... but at least we know the field value for this particular object.
               Value object = instancePut.object().getAliasedValue();
-              FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+              FieldAndObject fieldAndObject = new FieldAndObject(reference, object);
               ExistingValue value = new ExistingValue(instancePut.value());
-              if (isFinal(definition)) {
-                assert method.getDefinition().isInstanceInitializer()
+              if (isFinal(field)) {
+                assert !field.getDefinition().isFinal()
+                    || method.getDefinition().isInstanceInitializer()
                     || verifyWasInstanceInitializer();
                 activeState.putFinalInstanceField(fieldAndObject, value);
               } else {
@@ -229,7 +236,7 @@
               if (staticGet.outValue().hasLocalInfo()) {
                 continue;
               }
-              FieldValue replacement = activeState.getStaticFieldValue(field);
+              FieldValue replacement = activeState.getStaticFieldValue(reference);
               if (replacement != null) {
                 replacement.eliminateRedundantRead(it, staticGet);
               } else {
@@ -237,10 +244,10 @@
                 // field values.
                 killNonFinalActiveFields(staticGet);
                 FieldValue value = new ExistingValue(staticGet.value());
-                if (isFinal(definition)) {
-                  activeState.putFinalStaticField(field, value);
+                if (isFinal(field)) {
+                  activeState.putFinalStaticField(reference, value);
                 } else {
-                  activeState.putNonFinalStaticField(field, value);
+                  activeState.putNonFinalStaticField(reference, value);
                 }
               }
             } else if (instruction.isStaticPut()) {
@@ -249,11 +256,11 @@
               // field values.
               killNonFinalActiveFields(staticPut);
               ExistingValue value = new ExistingValue(staticPut.value());
-              if (definition.isFinal()) {
+              if (isFinal(field)) {
                 assert method.getDefinition().isClassInitializer();
-                activeState.putFinalStaticField(field, value);
+                activeState.putFinalStaticField(reference, value);
               } else {
-                activeState.putNonFinalStaticField(field, value);
+                activeState.putNonFinalStaticField(reference, value);
               }
             }
           } else if (instruction.isInitClass()) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java
index 7f5b905..bf78732 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java
@@ -62,15 +62,7 @@
         .compile()
         .addRunClasspathFiles(program)
         .run(parameters.getRuntime(), TestClass.class)
-        .apply(
-            runResult -> {
-              if (parameters.isCfRuntime()) {
-                // Constant canonicalization is disabled for CF.
-                runResult.assertSuccessWithEmptyOutput();
-              } else {
-                runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
-              }
-            });
+        .assertSuccessWithEmptyOutput();
   }
 
   private List<String> getAssumeValuesRule(int version) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index df009b4..e57d90b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -180,7 +180,6 @@
             "STATIC: String SimpleWithSideEffects.bar(String)",
             "STATIC: String SimpleWithSideEffects.foo()",
             "STATIC: String TrivialTestClass.next()",
-            "SimpleWithSideEffects SimpleWithSideEffects.INSTANCE",
             "SimpleWithSideEffects SimpleWithSideEffects.INSTANCE"),
         references(clazz, "testSimpleWithSideEffects", "void"));
 
@@ -315,7 +314,6 @@
             "STATIC: String movetohost.HostOkSideEffects.bar(String)",
             "STATIC: String movetohost.HostOkSideEffects.foo()",
             "STATIC: String movetohost.MoveToHostTestClass.next()",
-            "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE",
             "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
         references(clazz, "testOkSideEffects", "void"));