Reland "More aggressive redundant field load elimination for static final fields"

This reverts commit 1a50e3398c8f1cc19e26eb1ee6c488fe7afd977d.

Change-Id: If335861a634b64684cae45689aa0b1660bb8d50c
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e504e54..20216c4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
-import com.android.tools.r8.ir.optimize.library.LibraryMethodOptimizer;
+import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -57,7 +57,7 @@
 
   // Optimizations.
   private final CallSiteOptimizationInfoPropagator callSiteOptimizationInfoPropagator;
-  private final LibraryMethodOptimizer libraryMethodOptimizer;
+  private final LibraryMemberOptimizer libraryMemberOptimizer;
   private final ProtoShrinker protoShrinker;
 
   // Optimization results.
@@ -102,7 +102,7 @@
       this.callSiteOptimizationInfoPropagator = null;
     }
 
-    this.libraryMethodOptimizer = new LibraryMethodOptimizer(this);
+    this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
 
     if (enableWholeProgramOptimizations() && options.protoShrinking().isProtoShrinkingEnabled()) {
       this.protoShrinker = new ProtoShrinker(withLiveness());
@@ -113,7 +113,7 @@
 
   @Override
   public boolean isModeled(DexType type) {
-    return libraryMethodOptimizer.isModeled(type);
+    return libraryMemberOptimizer.isModeled(type);
   }
 
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo, InternalOptions options) {
@@ -247,8 +247,8 @@
     return callSiteOptimizationInfoPropagator;
   }
 
-  public LibraryMethodOptimizer libraryMethodOptimizer() {
-    return libraryMethodOptimizer;
+  public LibraryMemberOptimizer libraryMethodOptimizer() {
+    return libraryMemberOptimizer;
   }
 
   public ProtoShrinker protoShrinker() {
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 b0a86c6..b2d7140 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -398,12 +398,34 @@
   public final DexType reflectiveOperationExceptionType =
       createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
 
+  public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
+  public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
+  public final DexType javaNioByteOrderType = createStaticallyKnownType("Ljava/nio/ByteOrder;");
+  public final DexType javaUtilCollectionsType =
+      createStaticallyKnownType("Ljava/util/Collections;");
+  public final DexType javaUtilComparatorType = createStaticallyKnownType("Ljava/util/Comparator;");
+  public final DexType javaUtilConcurrentTimeUnitType =
+      createStaticallyKnownType("Ljava/util/concurrent/TimeUnit;");
+  public final DexType javaUtilListType = createStaticallyKnownType("Ljava/util/List;");
+  public final DexType javaUtilLocaleType = createStaticallyKnownType("Ljava/util/Locale;");
+  public final DexType javaUtilLoggingLevelType =
+      createStaticallyKnownType("Ljava/util/logging/Level;");
   public final DexType javaUtilLoggingLoggerType =
       createStaticallyKnownType("Ljava/util/logging/Logger;");
-  public final DexType androidUtilLogType = createStaticallyKnownType("Landroid/util/Log;");
+  public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;");
 
+  public final DexType androidOsBuildType = createStaticallyKnownType("Landroid/os/Build;");
   public final DexType androidOsBuildVersionType =
       createStaticallyKnownType("Landroid/os/Build$VERSION;");
+  public final DexType androidOsBundleType = createStaticallyKnownType("Landroid/os/Bundle;");
+  public final DexType androidOsParcelableCreatorType =
+      createStaticallyKnownType("Landroid/os/Parcelable$Creator;");
+  public final DexType androidSystemOsConstantsType =
+      createStaticallyKnownType("Landroid/system/OsConstants;");
+  public final DexType androidUtilLogType = createStaticallyKnownType("Landroid/util/Log;");
+  public final DexType androidUtilPropertyType =
+      createStaticallyKnownType("Landroid/util/Property;");
+  public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
 
   public final DexString nestConstructorDescriptor =
       createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
@@ -419,12 +441,13 @@
   public final StringBuildingMethods stringBufferMethods =
       new StringBuildingMethods(stringBufferType);
   public final BooleanMembers booleanMembers = new BooleanMembers();
+  public final FloatMembers floatMembers = new FloatMembers();
+  public final IntegerMembers integerMembers = new IntegerMembers();
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
   public final ObjectMembers objectMembers = new ObjectMembers();
-  public final StringMethods stringMethods = new StringMethods();
-  public final LongMethods longMethods = new LongMethods();
+  public final StringMembers stringMembers = new StringMembers();
+  public final LongMembers longMembers = new LongMembers();
   public final DoubleMethods doubleMethods = new DoubleMethods();
-  public final JavaUtilArraysMethods utilArraysMethods = new JavaUtilArraysMethods();
   public final ThrowableMethods throwableMethods = new ThrowableMethods();
   public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
   public final ClassMethods classMethods = new ClassMethods();
@@ -441,6 +464,51 @@
   public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
   public final ProxyMethods proxyMethods = new ProxyMethods();
 
+  // android.**
+  public final AndroidOsBuildMembers androidOsBuildMembers = new AndroidOsBuildMembers();
+  public final AndroidOsBuildVersionMembers androidOsBuildVersionMembers =
+      new AndroidOsBuildVersionMembers();
+  public final AndroidOsBundleMembers androidOsBundleMembers = new AndroidOsBundleMembers();
+  public final AndroidSystemOsConstantsMembers androidSystemOsConstantsMembers =
+      new AndroidSystemOsConstantsMembers();
+  public final AndroidViewViewMembers androidViewViewMembers = new AndroidViewViewMembers();
+
+  // java.**
+  public final JavaIoFileMembers javaIoFileMembers = new JavaIoFileMembers();
+  public final JavaMathBigIntegerMembers javaMathBigIntegerMembers =
+      new JavaMathBigIntegerMembers();
+  public final JavaNioByteOrderMembers javaNioByteOrderMembers = new JavaNioByteOrderMembers();
+  public final JavaUtilArraysMethods javaUtilArraysMethods = new JavaUtilArraysMethods();
+  public final JavaUtilComparatorMembers javaUtilComparatorMembers =
+      new JavaUtilComparatorMembers();
+  public final JavaUtilConcurrentTimeUnitMembers javaUtilConcurrentTimeUnitMembers =
+      new JavaUtilConcurrentTimeUnitMembers();
+  public final JavaUtilLocaleMembers javaUtilLocaleMembers = new JavaUtilLocaleMembers();
+  public final JavaUtilLoggingLevelMembers javaUtilLoggingLevelMembers =
+      new JavaUtilLoggingLevelMembers();
+
+  public final List<LibraryMembers> libraryMembersCollection =
+      ImmutableList.of(
+          booleanMembers,
+          floatMembers,
+          integerMembers,
+          longMembers,
+          stringMembers,
+          // android.**
+          androidOsBuildMembers,
+          androidOsBuildVersionMembers,
+          androidOsBundleMembers,
+          androidSystemOsConstantsMembers,
+          androidViewViewMembers,
+          // java.**
+          javaIoFileMembers,
+          javaMathBigIntegerMembers,
+          javaNioByteOrderMembers,
+          javaUtilComparatorMembers,
+          javaUtilConcurrentTimeUnitMembers,
+          javaUtilLocaleMembers,
+          javaUtilLoggingLevelMembers);
+
   public final DexString twrCloseResourceMethodName = createString("$closeResource");
   public final DexProto twrCloseResourceMethodProto =
       createProto(voidType, throwableType, autoCloseableType);
@@ -604,7 +672,7 @@
           objectsMethods.requireNonNull,
           objectsMethods.requireNonNullWithMessage,
           objectsMethods.requireNonNullWithMessageSupplier,
-          stringMethods.valueOf);
+          stringMembers.valueOf);
 
   // We assume library methods listed here are `public`, i.e., free from visibility side effects.
   // If not, that library method should not be added here because it literally has side effects.
@@ -672,7 +740,12 @@
     return dexMethod == metafactoryMethod || dexMethod == metafactoryAltMethod;
   }
 
-  public class BooleanMembers {
+  public interface LibraryMembers {
+
+    void forEachFinalField(Consumer<DexField> consumer);
+  }
+
+  public class BooleanMembers implements LibraryMembers {
 
     public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
     public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
@@ -684,16 +757,291 @@
         createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
 
     private BooleanMembers() {}
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(FALSE);
+      consumer.accept(TRUE);
+      consumer.accept(TYPE);
+    }
   }
 
-  public class LongMethods {
+  public class AndroidOsBuildMembers implements LibraryMembers {
+
+    public final DexField BOOTLOADER = createField(androidOsBuildType, stringType, "BOOTLOADER");
+    public final DexField BRAND = createField(androidOsBuildType, stringType, "BRAND");
+    public final DexField CPU_ABI = createField(androidOsBuildType, stringType, "CPU_ABI");
+    public final DexField CPU_ABI2 = createField(androidOsBuildType, stringType, "CPU_ABI2");
+    public final DexField DEVICE = createField(androidOsBuildType, stringType, "DEVICE");
+    public final DexField DISPLAY = createField(androidOsBuildType, stringType, "DISPLAY");
+    public final DexField FINGERPRINT = createField(androidOsBuildType, stringType, "FINGERPRINT");
+    public final DexField HARDWARE = createField(androidOsBuildType, stringType, "HARDWARE");
+    public final DexField MANUFACTURER =
+        createField(androidOsBuildType, stringType, "MANUFACTURER");
+    public final DexField MODEL = createField(androidOsBuildType, stringType, "MODEL");
+    public final DexField PRODUCT = createField(androidOsBuildType, stringType, "PRODUCT");
+    public final DexField SERIAL = createField(androidOsBuildType, stringType, "SERIAL");
+    public final DexField SUPPORTED_32_BIT_ABIS =
+        createField(androidOsBuildType, stringArrayType, "SUPPORTED_32_BIT_ABIS");
+    public final DexField SUPPORTED_64_BIT_ABIS =
+        createField(androidOsBuildType, stringArrayType, "SUPPORTED_64_BIT_ABIS");
+    public final DexField SUPPORTED_ABIS =
+        createField(androidOsBuildType, stringArrayType, "SUPPORTED_ABIS");
+    public final DexField TIME = createField(androidOsBuildType, longType, "TIME");
+    public final DexField TYPE = createField(androidOsBuildType, stringType, "TYPE");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(BOOTLOADER);
+      consumer.accept(BRAND);
+      consumer.accept(CPU_ABI);
+      consumer.accept(CPU_ABI2);
+      consumer.accept(DEVICE);
+      consumer.accept(DISPLAY);
+      consumer.accept(FINGERPRINT);
+      consumer.accept(HARDWARE);
+      consumer.accept(MANUFACTURER);
+      consumer.accept(MODEL);
+      consumer.accept(PRODUCT);
+      consumer.accept(SERIAL);
+      consumer.accept(SUPPORTED_32_BIT_ABIS);
+      consumer.accept(SUPPORTED_64_BIT_ABIS);
+      consumer.accept(SUPPORTED_ABIS);
+      consumer.accept(TIME);
+      consumer.accept(TYPE);
+    }
+  }
+
+  public class AndroidOsBuildVersionMembers implements LibraryMembers {
+
+    public final DexField CODENAME = createField(androidOsBuildVersionType, stringType, "CODENAME");
+    public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
+    public final DexField SDK = createField(androidOsBuildVersionType, stringType, "SDK");
+    public final DexField SDK_INT = createField(androidOsBuildVersionType, intType, "SDK_INT");
+    public final DexField SECURITY_PATCH =
+        createField(androidOsBuildVersionType, stringType, "SECURITY_PATCH");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CODENAME);
+      consumer.accept(RELEASE);
+      consumer.accept(SDK);
+      consumer.accept(SDK_INT);
+      consumer.accept(SECURITY_PATCH);
+    }
+  }
+
+  public class AndroidOsBundleMembers implements LibraryMembers {
+
+    public final DexField CREATOR =
+        createField(androidOsBundleType, androidOsParcelableCreatorType, "CREATOR");
+    public final DexField EMPTY = createField(androidOsBundleType, androidOsBundleType, "EMPTY");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CREATOR);
+      consumer.accept(EMPTY);
+    }
+  }
+
+  public class AndroidSystemOsConstantsMembers implements LibraryMembers {
+
+    public final DexField S_IRUSR = createField(androidSystemOsConstantsType, intType, "S_IRUSR");
+    public final DexField S_IXUSR = createField(androidSystemOsConstantsType, intType, "S_IXUSR");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(S_IRUSR);
+      consumer.accept(S_IXUSR);
+    }
+  }
+
+  public class AndroidViewViewMembers implements LibraryMembers {
+
+    public final DexField TRANSLATION_Z =
+        createField(androidViewViewType, androidUtilPropertyType, "TRANSLATION_Z");
+    public final DexField EMPTY_STATE_SET =
+        createField(androidViewViewType, intArrayType, "EMPTY_STATE_SET");
+    public final DexField ENABLED_STATE_SET =
+        createField(androidViewViewType, intArrayType, "ENABLED_STATE_SET");
+    public final DexField PRESSED_ENABLED_STATE_SET =
+        createField(androidViewViewType, intArrayType, "PRESSED_ENABLED_STATE_SET");
+    public final DexField SELECTED_STATE_SET =
+        createField(androidViewViewType, intArrayType, "SELECTED_STATE_SET");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TRANSLATION_Z);
+      consumer.accept(EMPTY_STATE_SET);
+      consumer.accept(ENABLED_STATE_SET);
+      consumer.accept(PRESSED_ENABLED_STATE_SET);
+      consumer.accept(SELECTED_STATE_SET);
+    }
+  }
+
+  public class FloatMembers implements LibraryMembers {
+
+    public final DexField TYPE = createField(boxedFloatType, classType, "TYPE");
+
+    private FloatMembers() {}
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TYPE);
+    }
+  }
+
+  public class JavaIoFileMembers implements LibraryMembers {
+
+    public final DexField pathSeparator = createField(javaIoFileType, stringType, "pathSeparator");
+    public final DexField separator = createField(javaIoFileType, stringType, "separator");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(pathSeparator);
+      consumer.accept(separator);
+    }
+  }
+
+  public class JavaMathBigIntegerMembers implements LibraryMembers {
+
+    public final DexField ONE = createField(javaMathBigIntegerType, javaMathBigIntegerType, "ONE");
+    public final DexField ZERO =
+        createField(javaMathBigIntegerType, javaMathBigIntegerType, "ZERO");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(ONE);
+      consumer.accept(ZERO);
+    }
+  }
+
+  public class JavaNioByteOrderMembers implements LibraryMembers {
+
+    public final DexField LITTLE_ENDIAN =
+        createField(javaNioByteOrderType, javaNioByteOrderType, "LITTLE_ENDIAN");
+    public final DexField BIG_ENDIAN =
+        createField(javaNioByteOrderType, javaNioByteOrderType, "BIG_ENDIAN");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(LITTLE_ENDIAN);
+      consumer.accept(BIG_ENDIAN);
+    }
+  }
+
+  public class JavaUtilArraysMethods {
+
+    public final DexMethod asList;
+
+    private JavaUtilArraysMethods() {
+      asList =
+          createMethod(
+              arraysDescriptor,
+              createString("asList"),
+              listDescriptor,
+              new DexString[] {objectArrayDescriptor});
+    }
+  }
+
+  public class JavaUtilComparatorMembers implements LibraryMembers {
+
+    public final DexField EMPTY_LIST =
+        createField(javaUtilCollectionsType, javaUtilListType, "EMPTY_LIST");
+    public final DexField EMPTY_SET =
+        createField(javaUtilCollectionsType, javaUtilSetType, "EMPTY_SET");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(EMPTY_LIST);
+      consumer.accept(EMPTY_SET);
+    }
+  }
+
+  public class JavaUtilConcurrentTimeUnitMembers implements LibraryMembers {
+
+    public final DexField DAYS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "DAYS");
+    public final DexField HOURS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "HOURS");
+    public final DexField MICROSECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MICROSECONDS");
+    public final DexField MILLISECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MILLISECONDS");
+    public final DexField MINUTES =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MINUTES");
+    public final DexField NANOSECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "NANOSECONDS");
+    public final DexField SECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "SECONDS");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(DAYS);
+      consumer.accept(HOURS);
+      consumer.accept(MICROSECONDS);
+      consumer.accept(MILLISECONDS);
+      consumer.accept(MINUTES);
+      consumer.accept(NANOSECONDS);
+      consumer.accept(SECONDS);
+    }
+  }
+
+  public class JavaUtilLocaleMembers implements LibraryMembers {
+
+    public final DexField ENGLISH = createField(javaUtilLocaleType, javaUtilLocaleType, "ENGLISH");
+    public final DexField ROOT = createField(javaUtilLocaleType, javaUtilLocaleType, "ROOT");
+    public final DexField US = createField(javaUtilLocaleType, javaUtilLocaleType, "US");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(ENGLISH);
+      consumer.accept(ROOT);
+      consumer.accept(US);
+    }
+  }
+
+  public class JavaUtilLoggingLevelMembers implements LibraryMembers {
+
+    public final DexField CONFIG =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "CONFIG");
+    public final DexField FINE =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINE");
+    public final DexField FINER =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINER");
+    public final DexField FINEST =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINEST");
+    public final DexField SEVERE =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "SEVERE");
+    public final DexField WARNING =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "WARNING");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CONFIG);
+      consumer.accept(FINE);
+      consumer.accept(FINER);
+      consumer.accept(FINEST);
+      consumer.accept(SEVERE);
+      consumer.accept(WARNING);
+    }
+  }
+
+  public class LongMembers implements LibraryMembers {
+
+    public final DexField TYPE = createField(boxedLongType, classType, "TYPE");
 
     public final DexMethod compare;
 
-    private LongMethods() {
+    private LongMembers() {
       compare = createMethod(boxedLongDescriptor,
           createString("compare"), intDescriptor, new DexString[]{longDescriptor, longDescriptor});
     }
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TYPE);
+    }
   }
 
   public class DoubleMethods {
@@ -710,17 +1058,13 @@
     }
   }
 
-  public class JavaUtilArraysMethods {
+  public class IntegerMembers implements LibraryMembers {
 
-    public final DexMethod asList;
+    public final DexField TYPE = createField(boxedIntType, classType, "TYPE");
 
-    private JavaUtilArraysMethods() {
-      asList =
-          createMethod(
-              arraysDescriptor,
-              createString("asList"),
-              listDescriptor,
-              new DexString[] {objectArrayDescriptor});
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TYPE);
     }
   }
 
@@ -1072,7 +1416,11 @@
     }
   }
 
-  public class StringMethods {
+  public class StringMembers implements LibraryMembers {
+
+    public final DexField CASE_INSENSITIVE_ORDER =
+        createField(stringType, javaUtilComparatorType, "CASE_INSENSITIVE_ORDER");
+
     public final DexMethod isEmpty;
     public final DexMethod length;
 
@@ -1098,7 +1446,7 @@
 
     public final DexMethod trim = createMethod(stringType, createProto(stringType), trimName);
 
-    private StringMethods() {
+    private StringMembers() {
       isEmpty = createMethod(
           stringDescriptor, isEmptyMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
       length = createMethod(
@@ -1145,6 +1493,11 @@
       intern = createMethod(
           stringDescriptor, internMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
     }
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CASE_INSENSITIVE_ORDER);
+    }
   }
 
   public class StringBuildingMethods {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 05416df..f417d65 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -20,16 +20,12 @@
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -119,9 +115,6 @@
       if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
         return false;
       }
-      if (isAliasOfValueThatIsIndependentOfEnvironment(root, assumedNotToDependOnEnvironment)) {
-        return false;
-      }
       return true;
     } finally {
       boolean changed = visited.remove(root);
@@ -414,49 +407,4 @@
 
     return false;
   }
-
-  private boolean isAliasOfValueThatIsIndependentOfEnvironment(
-      Value value, Set<Value> assumedNotToDependOnEnvironment) {
-    // If we are inside a class initializer, and we are reading a final field of the enclosing
-    // class, then check if there is a single write to that field in the class initializer, and that
-    // the value being written into that field does not depend on the environment.
-    //
-    // The reason why we do not currently treat final fields that are written in multiple places is
-    // that the value of the field could then be dependent on the environment due to the control
-    // flow.
-    if (code.method.isClassInitializer()) {
-      assert !value.hasAliasedValue();
-      if (value.isPhi()) {
-        return false;
-      }
-      Instruction definition = value.definition;
-      if (definition.isStaticGet()) {
-        StaticGet staticGet = definition.asStaticGet();
-        DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
-        if (field != null && field.holder() == context) {
-          List<StaticPut> finalFieldPuts = computeFinalFieldPuts().get(field);
-          if (finalFieldPuts == null || finalFieldPuts.size() != 1) {
-            return false;
-          }
-          StaticPut staticPut = ListUtils.first(finalFieldPuts);
-          return !valueMayDependOnEnvironment(staticPut.value(), assumedNotToDependOnEnvironment);
-        }
-      }
-    }
-    return false;
-  }
-
-  private Map<DexEncodedField, List<StaticPut>> computeFinalFieldPuts() {
-    assert code.method.isClassInitializer();
-    if (finalFieldPuts == null) {
-      finalFieldPuts = new IdentityHashMap<>();
-      for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
-        DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
-        if (field != null && field.holder() == context && field.isFinal()) {
-          finalFieldPuts.computeIfAbsent(field, ignore -> new ArrayList<>()).add(staticPut);
-        }
-      }
-    }
-    return finalFieldPuts;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index 20ab05c..0365d43 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -187,7 +187,7 @@
     }
     Instruction definition = root.definition;
     return definition.isInvokeVirtual()
-        && definition.asInvokeVirtual().getInvokedMethod() == dexItemFactory.stringMethods.hashCode;
+        && definition.asInvokeVirtual().getInvokedMethod() == dexItemFactory.stringMembers.hashCode;
   }
 
   static class StringSwitchBuilderInfo {
@@ -376,7 +376,7 @@
           }
           if (instruction.isInvokeVirtual()) {
             InvokeVirtual invoke = instruction.asInvokeVirtual();
-            if (invoke.getInvokedMethod() == dexItemFactory.stringMethods.hashCode
+            if (invoke.getInvokedMethod() == dexItemFactory.stringMembers.hashCode
                 && invoke.getReceiver() == stringValue
                 && invoke.outValue().onlyUsedInBlock(block)) {
               continue;
@@ -503,7 +503,7 @@
 
         InvokeVirtual theInvoke = instructionIterator.next().asInvokeVirtual();
         if (theInvoke == null
-            || theInvoke.getInvokedMethod() != dexItemFactory.stringMethods.equals
+            || theInvoke.getInvokedMethod() != dexItemFactory.stringMembers.equals
             || theInvoke.getReceiver() != stringValue
             || theInvoke.inValues().get(1) != theString.outValue()) {
           return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index b1f53e1..fcfc1e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -108,7 +108,7 @@
 
       InvokeVirtual invokeInstruction =
           new InvokeVirtual(
-              appView.dexItemFactory().stringMethods.equals,
+              appView.dexItemFactory().stringMembers.equals,
               code.createValue(PrimitiveTypeElement.getInt()),
               ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
       invokeInstruction.setPosition(Position.syntheticNone());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
index 74b1631..f0b74ea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
@@ -70,7 +70,7 @@
     }
 
     builder.add(
-        new CfInvoke(INVOKESTATIC, factory.utilArraysMethods.asList, false),
+        new CfInvoke(INVOKESTATIC, factory.javaUtilArraysMethods.asList, false),
         new CfInvoke(
             INVOKEINTERFACE,
             factory.createMethod(
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 9f15bcb..9a9899e 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
@@ -142,6 +142,13 @@
     }
   }
 
+  public boolean isFinal(DexEncodedField field) {
+    if (field.isProgramField(appView)) {
+      return field.isFinal();
+    }
+    return appView.libraryMethodOptimizer().isFinalLibraryField(field);
+  }
+
   private DexEncodedField resolveField(DexField field) {
     if (appView.enableWholeProgramOptimizations()) {
       return appView.appInfo().resolveField(field);
@@ -198,7 +205,7 @@
             Value object = instancePut.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
             ExistingValue value = new ExistingValue(instancePut.value());
-            if (definition.isFinal()) {
+            if (isFinal(definition)) {
               assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
               activeState.putFinalInstanceField(fieldAndObject, value);
             } else {
@@ -216,7 +223,12 @@
               // A field get on a different class can cause <clinit> to run and change static
               // field values.
               killNonFinalActiveFields(staticGet);
-              activeState.putNonFinalStaticField(field, new ExistingValue(staticGet.value()));
+              FieldValue value = new ExistingValue(staticGet.value());
+              if (isFinal(definition)) {
+                activeState.putFinalStaticField(field, value);
+              } else {
+                activeState.putNonFinalStaticField(field, value);
+              }
             }
           } else if (instruction.isStaticPut()) {
             StaticPut staticPut = instruction.asStaticPut();
@@ -558,9 +570,9 @@
       if (instruction.isInstanceGet()) {
         Value object = instruction.asInstanceGet().object().getAliasedValue();
         FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-        removeNonFinalInstanceField(fieldAndObject);
+        removeInstanceField(fieldAndObject);
       } else if (instruction.isStaticGet()) {
-        removeNonFinalStaticField(field);
+        removeStaticField(field);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
similarity index 78%
rename from src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
rename to src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index a3acde6..46a1d7c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory.LibraryMembers;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
@@ -21,17 +23,20 @@
 import java.util.Map;
 import java.util.Set;
 
-public class LibraryMethodOptimizer implements CodeOptimization {
+public class LibraryMemberOptimizer implements CodeOptimization {
 
   private final AppView<?> appView;
 
+  /** Library fields that are assumed to be final. */
+  private final Set<DexEncodedField> finalLibraryFields = Sets.newIdentityHashSet();
+
   /** The library types that are modeled. */
   private final Set<DexType> modeledLibraryTypes = Sets.newIdentityHashSet();
 
   private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
       new IdentityHashMap<>();
 
-  public LibraryMethodOptimizer(AppView<?> appView) {
+  public LibraryMemberOptimizer(AppView<?> appView) {
     this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
     register(new ObjectMethodOptimizer(appView));
@@ -46,12 +51,35 @@
       register(new LogMethodOptimizer(appView));
     }
 
+    initializeFinalLibraryFields();
+
     LibraryOptimizationInfoInitializer libraryOptimizationInfoInitializer =
         new LibraryOptimizationInfoInitializer(appView);
     libraryOptimizationInfoInitializer.run();
     modeledLibraryTypes.addAll(libraryOptimizationInfoInitializer.getModeledLibraryTypes());
   }
 
+  private void initializeFinalLibraryFields() {
+    for (LibraryMembers libraryMembers : appView.dexItemFactory().libraryMembersCollection) {
+      libraryMembers.forEachFinalField(
+          field -> {
+            DexEncodedField definition = appView.definitionFor(field);
+            if (definition != null) {
+              if (definition.isFinal()) {
+                finalLibraryFields.add(definition);
+              } else {
+                assert false : "Field `" + field.toSourceString() + "` is not final";
+              }
+            }
+          });
+    }
+  }
+
+  /** Returns true if it is safe to assume that the given library field is final. */
+  public boolean isFinalLibraryField(DexEncodedField field) {
+    return finalLibraryFields.contains(field);
+  }
+
   /**
    * Returns true if {@param type} is a library type that is modeled in the compiler.
    *
@@ -78,9 +106,7 @@
 
   @Override
   public void optimize(
-      IRCode code,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor) {
+      IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator instructionIterator = code.instructionListIterator();
     while (instructionIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 09c8f4e..0713076 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -39,7 +39,7 @@
       InvokeMethod invoke,
       DexEncodedMethod singleTarget,
       Set<Value> affectedValues) {
-    if (singleTarget.method == dexItemFactory.stringMethods.equals) {
+    if (singleTarget.method == dexItemFactory.stringMembers.equals) {
       optimizeEquals(code, instructionIterator, invoke);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 140c844..07d330b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -852,7 +852,7 @@
     public boolean isToStringMethod(DexMethod method) {
       return method == factory.stringBuilderMethods.toString
           || method == factory.stringBufferMethods.toString
-          || method == factory.stringMethods.valueOf;
+          || method == factory.stringMembers.valueOf;
     }
 
     private boolean canHandleArgumentType(DexType argType) {
@@ -910,7 +910,7 @@
             for (Instruction outUser : out.uniqueUsers()) {
               if (outUser.isInvokeMethodWithReceiver()
                   && outUser.asInvokeMethodWithReceiver().getInvokedMethod()
-                      == factory.stringMethods.intern) {
+                      == factory.stringMembers.intern) {
                 numberOfBuildersWhoseResultIsInterned++;
                 return false;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index a2b8eb5..cc8a24f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -191,7 +191,7 @@
         continue;
       }
 
-      if (invokedMethod == factory.stringMethods.trim) {
+      if (invokedMethod == factory.stringMembers.trim) {
         Value receiver = invoke.getReceiver().getAliasedValue();
         if (receiver.hasLocalInfo() || receiver.isPhi() || !receiver.definition.isConstString()) {
           continue;
@@ -210,7 +210,7 @@
       Function<DexString, Integer> operatorWithNoArg = null;
       BiFunction<DexString, DexString, Integer> operatorWithString = null;
       BiFunction<DexString, Integer, Integer> operatorWithInt = null;
-      if (invokedMethod == factory.stringMethods.hashCode) {
+      if (invokedMethod == factory.stringMembers.hashCode) {
         operatorWithNoArg = rcv -> {
           try {
             return rcv.decodedHashCode();
@@ -219,33 +219,33 @@
             throw new Unreachable();
           }
         };
-      } else if (invokedMethod == factory.stringMethods.length) {
+      } else if (invokedMethod == factory.stringMembers.length) {
         operatorWithNoArg = rcv -> rcv.size;
-      } else if (invokedMethod == factory.stringMethods.isEmpty) {
+      } else if (invokedMethod == factory.stringMembers.isEmpty) {
         operatorWithNoArg = rcv -> rcv.size == 0 ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.contains) {
+      } else if (invokedMethod == factory.stringMembers.contains) {
         operatorWithString = (rcv, arg) -> rcv.toString().contains(arg.toString()) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.startsWith) {
+      } else if (invokedMethod == factory.stringMembers.startsWith) {
         operatorWithString = (rcv, arg) -> rcv.startsWith(arg) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.endsWith) {
+      } else if (invokedMethod == factory.stringMembers.endsWith) {
         operatorWithString = (rcv, arg) -> rcv.endsWith(arg) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.equals) {
+      } else if (invokedMethod == factory.stringMembers.equals) {
         operatorWithString = (rcv, arg) -> rcv.equals(arg) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.equalsIgnoreCase) {
+      } else if (invokedMethod == factory.stringMembers.equalsIgnoreCase) {
         operatorWithString = (rcv, arg) -> rcv.toString().equalsIgnoreCase(arg.toString()) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
+      } else if (invokedMethod == factory.stringMembers.contentEqualsCharSequence) {
         operatorWithString = (rcv, arg) -> rcv.toString().contentEquals(arg.toString()) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.indexOfInt) {
+      } else if (invokedMethod == factory.stringMembers.indexOfInt) {
         operatorWithInt = (rcv, idx) -> rcv.toString().indexOf(idx);
-      } else if (invokedMethod == factory.stringMethods.indexOfString) {
+      } else if (invokedMethod == factory.stringMembers.indexOfString) {
         operatorWithString = (rcv, arg) -> rcv.toString().indexOf(arg.toString());
-      } else if (invokedMethod == factory.stringMethods.lastIndexOfInt) {
+      } else if (invokedMethod == factory.stringMembers.lastIndexOfInt) {
         operatorWithInt = (rcv, idx) -> rcv.toString().lastIndexOf(idx);
-      } else if (invokedMethod == factory.stringMethods.lastIndexOfString) {
+      } else if (invokedMethod == factory.stringMembers.lastIndexOfString) {
         operatorWithString = (rcv, arg) -> rcv.toString().lastIndexOf(arg.toString());
-      } else if (invokedMethod == factory.stringMethods.compareTo) {
+      } else if (invokedMethod == factory.stringMembers.compareTo) {
         operatorWithString = (rcv, arg) -> rcv.toString().compareTo(arg.toString());
-      } else if (invokedMethod == factory.stringMethods.compareToIgnoreCase) {
+      } else if (invokedMethod == factory.stringMembers.compareToIgnoreCase) {
         operatorWithString = (rcv, arg) -> rcv.toString().compareToIgnoreCase(arg.toString());
       } else {
         continue;
@@ -513,7 +513,7 @@
       if (instr.isInvokeStatic()) {
         InvokeStatic invoke = instr.asInvokeStatic();
         DexMethod invokedMethod = invoke.getInvokedMethod();
-        if (invokedMethod != factory.stringMethods.valueOf) {
+        if (invokedMethod != factory.stringMembers.valueOf) {
           continue;
         }
         assert invoke.inValues().size() == 1;
@@ -546,7 +546,7 @@
       } else if (instr.isInvokeVirtual()) {
         InvokeVirtual invoke = instr.asInvokeVirtual();
         DexMethod invokedMethod = invoke.getInvokedMethod();
-        if (invokedMethod != factory.stringMethods.toString) {
+        if (invokedMethod != factory.stringMembers.toString) {
           continue;
         }
         assert invoke.inValues().size() == 1;
@@ -596,9 +596,9 @@
       if (escapeRoute.isInvokeMethod()) {
         DexMethod invokedMethod = escapeRoute.asInvokeMethod().getInvokedMethod();
         // b/120138731: Only allow known simple operations on const-string
-        if (invokedMethod == appView.dexItemFactory().stringMethods.hashCode
-            || invokedMethod == appView.dexItemFactory().stringMethods.isEmpty
-            || invokedMethod == appView.dexItemFactory().stringMethods.length) {
+        if (invokedMethod == appView.dexItemFactory().stringMembers.hashCode
+            || invokedMethod == appView.dexItemFactory().stringMembers.isEmpty
+            || invokedMethod == appView.dexItemFactory().stringMembers.length) {
           return true;
         }
         // Add more cases to filter out, if any.
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index d8d62ae..b2f5a80 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -173,7 +173,7 @@
   }
 
   static boolean isClassNameComparison(InvokeVirtual invoke, DexItemFactory dexItemFactory) {
-    return invoke.getInvokedMethod() == dexItemFactory.stringMethods.equals
+    return invoke.getInvokedMethod() == dexItemFactory.stringMembers.equals
         && (isClassNameValue(invoke.getReceiver(), dexItemFactory)
             || isClassNameValue(invoke.inValues().get(1), dexItemFactory));
   }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
index 971818b..9644e0d 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
@@ -123,7 +123,7 @@
       Instruction instruction, DexItemFactory dexItemFactory) {
     // Intentionally using toSourceString() because `instruction.getInvokedMethod()` belongs to
     // another factory than the given `dexItemFactory`.
-    String signature = dexItemFactory.stringMethods.hashCode.toSourceString();
+    String signature = dexItemFactory.stringMembers.hashCode.toSourceString();
     return instruction.isInvokeVirtual()
         && instruction.asInvokeVirtual().getInvokedMethod().toSourceString().equals(signature);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
index 1e323fb..589fa3f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
@@ -68,8 +68,7 @@
     MethodSubject mainMethodSubject = classSubject.mainMethod();
     assertThat(mainMethodSubject, isPresent());
     assertEquals(
-        // No canonicalization when generating class files.
-        parameters.isCfRuntime() ? 3 : 1,
+        1,
         mainMethodSubject
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
@@ -77,7 +76,7 @@
             .filter(enumFieldSubject.getField().field::equals)
             .count());
     assertEquals(
-        3,
+        1,
         mainMethodSubject
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
index fe3d79a..2b910f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
@@ -65,9 +65,8 @@
 
     MethodSubject mMethodSubject = aClassSubject.uniqueMethodWithName("m");
     assertThat(mMethodSubject, isPresent());
-    // TODO(b/152196923): Should be 0.
     assertEquals(
-        2,
+        1,
         countStaticGetInstructions(
             mMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
   }