Model Boolean.booleanValue() and Boolean.valueOf()

Bug: 144419767, 145259212, 145253152
Change-Id: Ieb04e3b726250465c7ee965b263f7e21e2a38e7d
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 1174f5e..40c60ac 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -344,6 +344,7 @@
       new StringBuildingMethods(stringBuilderType);
   public final StringBuildingMethods stringBufferMethods =
       new StringBuildingMethods(stringBufferType);
+  public final BooleanMembers booleanMembers = new BooleanMembers();
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
   public final ObjectMethods objectMethods = new ObjectMethods();
   public final StringMethods stringMethods = new StringMethods();
@@ -552,7 +553,7 @@
           .build();
 
   public final Set<DexType> libraryClassesWithoutStaticInitialization =
-      ImmutableSet.of(enumType, objectType, stringBufferType, stringBuilderType);
+      ImmutableSet.of(boxedBooleanType, enumType, objectType, stringBufferType, stringBuilderType);
 
   private boolean skipNameValidationForTesting = false;
 
@@ -568,6 +569,20 @@
     return dexMethod == metafactoryMethod || dexMethod == metafactoryAltMethod;
   }
 
+  public class BooleanMembers {
+
+    public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
+    public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
+    public final DexField TYPE = createField(boxedBooleanType, classType, "TYPE");
+
+    public final DexMethod booleanValue =
+        createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
+    public final DexMethod valueOf =
+        createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
+
+    private BooleanMembers() {}
+  }
+
   public class LongMethods {
 
     public final DexMethod compare;
@@ -806,7 +821,7 @@
    * E.g. for Boolean https://docs.oracle.com/javase/8/docs/api/java/lang/Boolean.html#TYPE.
    */
   public class PrimitiveTypesBoxedTypeFields {
-    public final DexField booleanTYPE;
+
     public final DexField byteTYPE;
     public final DexField charTYPE;
     public final DexField shortTYPE;
@@ -818,7 +833,6 @@
     private final Map<DexField, DexType> boxedFieldTypeToPrimitiveType;
 
     private PrimitiveTypesBoxedTypeFields() {
-      booleanTYPE = createField(boxedBooleanType, classType, "TYPE");
       byteTYPE = createField(boxedByteType, classType, "TYPE");
       charTYPE = createField(boxedCharType, classType, "TYPE");
       shortTYPE = createField(boxedShortType, classType, "TYPE");
@@ -829,7 +843,7 @@
 
       boxedFieldTypeToPrimitiveType =
           ImmutableMap.<DexField, DexType>builder()
-              .put(booleanTYPE, booleanType)
+              .put(booleanMembers.TYPE, booleanType)
               .put(byteTYPE, byteType)
               .put(charTYPE, charType)
               .put(shortTYPE, shortType)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 28ac1a2..64401fd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -34,6 +34,11 @@
     return this;
   }
 
+  public boolean getBooleanValue() {
+    assert value == 0 || value == 1;
+    return value != 0;
+  }
+
   public long getValue() {
     return value;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 708e947..085c936 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -4,10 +4,12 @@
 
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -218,6 +220,55 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithConstInt(
+      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+    if (current == null) {
+      throw new IllegalStateException();
+    }
+
+    assert !current.hasOutValue() || current.outValue().getTypeLattice().isInt();
+
+    // Replace the instruction by const-number.
+    ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo());
+    for (Value inValue : current.inValues()) {
+      if (inValue.hasLocalInfo()) {
+        // Add this value as a debug value to avoid changing its live range.
+        constNumber.addDebugValue(inValue);
+      }
+    }
+    replaceCurrentInstruction(constNumber);
+  }
+
+  @Override
+  public void replaceCurrentInstructionWithStaticGet(
+      AppView<? extends AppInfoWithSubtyping> appView,
+      IRCode code,
+      DexField field,
+      Set<Value> affectedValues) {
+    if (current == null) {
+      throw new IllegalStateException();
+    }
+
+    // Replace the instruction by static-get.
+    TypeLatticeElement newType = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+    TypeLatticeElement oldType = current.hasOutValue() ? current.outValue().getTypeLattice() : null;
+    Value value = code.createValue(newType, current.getLocalInfo());
+    StaticGet staticGet = new StaticGet(value, field);
+    for (Value inValue : current.inValues()) {
+      if (inValue.hasLocalInfo()) {
+        // Add this value as a debug value to avoid changing its live range.
+        staticGet.addDebugValue(inValue);
+      }
+    }
+    replaceCurrentInstruction(staticGet);
+
+    // Update affected values.
+    if (value.hasAnyUsers() && !newType.equals(oldType)) {
+      affectedValues.addAll(value.affectedValues());
+    }
+  }
+
+  @Override
   public void replaceCurrentInstructionWithThrowNull(
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 60e06c6..fdea0f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ListIterator;
@@ -37,6 +38,22 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithConstInt(
+      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+    instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+  }
+
+  @Override
+  public void replaceCurrentInstructionWithStaticGet(
+      AppView<? extends AppInfoWithSubtyping> appView,
+      IRCode code,
+      DexField field,
+      Set<Value> affectedValues) {
+    instructionIterator.replaceCurrentInstructionWithStaticGet(
+        appView, code, field, affectedValues);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithThrowNull(
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index f4ff24a..315568e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
@@ -57,6 +58,15 @@
 
   Value insertConstIntInstruction(IRCode code, InternalOptions options, int value);
 
+  void replaceCurrentInstructionWithConstInt(
+      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value);
+
+  void replaceCurrentInstructionWithStaticGet(
+      AppView<? extends AppInfoWithSubtyping> appView,
+      IRCode code,
+      DexField field,
+      Set<Value> affectedValues);
+
   /**
    * Replace the current instruction with null throwing instructions.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 2c76690..3a098ca 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ListIterator;
@@ -50,6 +51,22 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithConstInt(
+      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+    currentBlockIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+  }
+
+  @Override
+  public void replaceCurrentInstructionWithStaticGet(
+      AppView<? extends AppInfoWithSubtyping> appView,
+      IRCode code,
+      DexField field,
+      Set<Value> affectedValues) {
+    currentBlockIterator.replaceCurrentInstructionWithStaticGet(
+        appView, code, field, affectedValues);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithThrowNull(
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 97d42fb..dca6174 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -12,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -1126,7 +1127,7 @@
       DexType type = definition.asNewInstance().clazz;
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && !clazz.isInterface()) {
-        return ClassTypeLatticeElement.create(type, Nullability.definitelyNotNull(), appView);
+        return ClassTypeLatticeElement.create(type, definitelyNotNull(), appView);
       }
       return null;
     }
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 fd1b111..3e9ecab 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
@@ -73,6 +73,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
+import com.android.tools.r8.ir.optimize.library.LibraryMethodOptimizer;
 import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -126,6 +127,7 @@
   private final Outliner outliner;
   private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization;
   private final FieldBitAccessAnalysis fieldBitAccessAnalysis;
+  private final LibraryMethodOptimizer libraryMethodOptimizer;
   private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
   private final StringConcatRewriter stringConcatRewriter;
   private final StringOptimizer stringOptimizer;
@@ -225,6 +227,7 @@
       this.classInliner = null;
       this.classStaticizer = null;
       this.fieldBitAccessAnalysis = null;
+      this.libraryMethodOptimizer = null;
       this.libraryMethodOverrideAnalysis = null;
       this.inliner = null;
       this.outliner = null;
@@ -286,6 +289,7 @@
           options.enableFieldBitAccessAnalysis
               ? new FieldBitAccessAnalysis(appViewWithLiveness)
               : null;
+      this.libraryMethodOptimizer = new LibraryMethodOptimizer(appViewWithLiveness);
       this.libraryMethodOverrideAnalysis =
           options.enableTreeShakingOfLibraryMethodOverrides
               ? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
@@ -322,6 +326,7 @@
       this.classStaticizer = null;
       this.dynamicTypeOptimization = null;
       this.fieldBitAccessAnalysis = null;
+      this.libraryMethodOptimizer = null;
       this.libraryMethodOverrideAnalysis = null;
       this.inliner = null;
       this.lambdaMerger = null;
@@ -1185,6 +1190,9 @@
       // Reflection/string optimization 3. trivial conversion/computation on const-string
       stringOptimizer.computeTrivialOperationsOnConstString(code);
       stringOptimizer.removeTrivialConversions(code);
+      if (libraryMethodOptimizer != null) {
+        libraryMethodOptimizer.optimize(appView, code, feedback, methodProcessor);
+      }
       assert code.isConsistentSSA();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 0195c84..303b4ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -154,7 +154,7 @@
             }
           }
         }
-        // TODO(b/119596718): Use dominant tree to extend it to non-canonicalized in values?
+        // TODO(b/145259212): Use dominant tree to extend it to non-canonicalized in values?
         // For now, interested in inputs that are also canonicalized constants.
         boolean invocationCanBeMovedToEntryBlock = true;
         for (Value in : current.inValues()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
new file mode 100644
index 0000000..edd657b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2019, 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.library;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+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.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+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.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class BooleanMethodOptimizer implements LibraryMethodModelCollection {
+
+  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final DexItemFactory dexItemFactory;
+
+  BooleanMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  public DexType getType() {
+    return dexItemFactory.boxedBooleanType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    if (singleTarget.method == dexItemFactory.booleanMembers.booleanValue) {
+      optimizeBooleanValue(code, instructionIterator, invoke);
+    } else if (singleTarget.method == dexItemFactory.booleanMembers.valueOf) {
+      optimizeValueOf(code, instructionIterator, invoke, affectedValues);
+    }
+  }
+
+  private void optimizeBooleanValue(
+      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+    Value argument = invoke.arguments().get(0).getAliasedValue();
+    if (!argument.isPhi()) {
+      Instruction definition = argument.definition;
+      if (definition.isStaticGet()) {
+        StaticGet staticGet = definition.asStaticGet();
+        DexField field = staticGet.getField();
+        if (field == dexItemFactory.booleanMembers.TRUE) {
+          instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 1);
+        } else if (field == dexItemFactory.booleanMembers.FALSE) {
+          instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 0);
+        }
+      }
+    }
+  }
+
+  private void optimizeValueOf(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      Set<Value> affectedValues) {
+    Value argument = invoke.arguments().get(0);
+    AbstractValue abstractValue = argument.getAbstractValue(appView, code.method.method.holder);
+    if (abstractValue.isSingleNumberValue()) {
+      instructionIterator.replaceCurrentInstructionWithStaticGet(
+          appView,
+          code,
+          abstractValue.asSingleNumberValue().getBooleanValue()
+              ? dexItemFactory.booleanMembers.TRUE
+              : dexItemFactory.booleanMembers.FALSE,
+          affectedValues);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
new file mode 100644
index 0000000..5608b94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2019, 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.library;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+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 java.util.Set;
+
+/** Used to model the behavior of library methods for optimization purposes. */
+public interface LibraryMethodModelCollection {
+
+  /**
+   * The library class whose methods are being modeled by this collection of models. As an example,
+   * {@link BooleanMethodOptimizer} is modeling {@link Boolean}).
+   */
+  DexType getType();
+
+  /**
+   * Invoked for instructions in {@param code} that invoke a method on the class returned by {@link
+   * #getType()}. The given {@param singleTarget} is guaranteed to be non-null.
+   */
+  void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues);
+}
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/LibraryMethodOptimizer.java
new file mode 100644
index 0000000..0a92679
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2019, 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.library;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+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.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.CodeOptimization;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class LibraryMethodOptimizer implements CodeOptimization {
+
+  private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
+      new IdentityHashMap<>();
+
+  public LibraryMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+    register(new BooleanMethodOptimizer(appView));
+  }
+
+  private void register(LibraryMethodModelCollection optimizer) {
+    LibraryMethodModelCollection existing =
+        libraryMethodModelCollections.put(optimizer.getType(), optimizer);
+    assert existing == null;
+  }
+
+  @Override
+  public void optimize(
+      AppView<?> appView,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor) {
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    InstructionListIterator instructionIterator = code.instructionListIterator();
+    while (instructionIterator.hasNext()) {
+      Instruction instruction = instructionIterator.next();
+      if (instruction.isInvokeMethod()) {
+        InvokeMethod invoke = instruction.asInvokeMethod();
+        DexEncodedMethod singleTarget =
+            invoke.lookupSingleTarget(appView, code.method.method.holder);
+        if (singleTarget != null) {
+          optimizeInvoke(code, instructionIterator, invoke, singleTarget, affectedValues);
+        }
+      }
+    }
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+  }
+
+  private void optimizeInvoke(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    LibraryMethodModelCollection optimizer =
+        libraryMethodModelCollections.getOrDefault(
+            singleTarget.method.holder, NopLibraryMethodModelCollection.getInstance());
+    optimizer.optimize(code, instructionIterator, invoke, singleTarget, affectedValues);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
new file mode 100644
index 0000000..9a0980f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2019, 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.library;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+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 java.util.Set;
+
+public class NopLibraryMethodModelCollection implements LibraryMethodModelCollection {
+
+  private static final NopLibraryMethodModelCollection INSTANCE =
+      new NopLibraryMethodModelCollection();
+
+  private NopLibraryMethodModelCollection() {}
+
+  public static NopLibraryMethodModelCollection getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public DexType getType() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java
index c1b808b..d3a87fa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java
@@ -68,12 +68,14 @@
     {
       Map<String, Boolean> map = new HashMap<>();
       // After canonicalization, only one 'true' and one 'false' conversions remain.
-      map.put("A", true);
-      map.put("B", true);
-      map.put("C", false);
-      map.put("D", true);
-      map.put("E", false);
-      map.put("F", true);
+      boolean alwaysTrue = System.currentTimeMillis() >= 0;
+      boolean alwaysFalse = System.currentTimeMillis() < 0;
+      map.put("A", alwaysTrue);
+      map.put("B", alwaysTrue);
+      map.put("C", alwaysFalse);
+      map.put("D", alwaysTrue);
+      map.put("E", alwaysFalse);
+      map.put("F", alwaysTrue);
       System.out.println(map.get("B"));
       System.out.println(map.get("E"));
     }
@@ -207,9 +209,12 @@
     test(
         result,
         TOTAL_MAX_CALLS,
-        EXPECTED_BOOLEAN_VALUE_OF,
-        EXPECTED_INTEGER_VALUE_OF,
-        EXPECTED_LONG_VALUE_OF);
+        // TODO(b/145259212): Should be `EXPECTED_BOOLEAN_VALUE_OF` (2).
+        6,
+        // TODO(b/145253152): Should be `EXPECTED_INTEGER_VALUE_OF` (2).
+        5,
+        // TODO(b/145253152): Should be `EXPECTED_LONG_VALUE_OF` (7).
+        10);
 
     result =
         testForD8()
@@ -221,9 +226,12 @@
     test(
         result,
         TOTAL_MAX_CALLS,
-        EXPECTED_BOOLEAN_VALUE_OF,
-        EXPECTED_INTEGER_VALUE_OF,
-        EXPECTED_LONG_VALUE_OF);
+        // TODO(b/145259212): Should be `EXPECTED_BOOLEAN_VALUE_O` (2).
+        6,
+        // TODO(b/145253152): Should be `EXPECTED_INTEGER_VALUE_OF` (2).
+        5,
+        // TODO(b/145253152): Should be `EXPECTED_LONG_VALUE_OF` (7).
+        10);
   }
 
   @Test
@@ -238,9 +246,12 @@
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     int expectedMaxCount = parameters.isCfRuntime() ? TOTAL_MAX_CALLS : EXPECTED_MAX_CALLS;
-    int expectedBooleanValueOfCount = parameters.isCfRuntime() ? 6 : EXPECTED_BOOLEAN_VALUE_OF;
-    int expectedIntValueOfCount = parameters.isCfRuntime() ? 5 : EXPECTED_INTEGER_VALUE_OF;
-    int expectedLongValueOfCount = parameters.isCfRuntime() ? 10 : EXPECTED_LONG_VALUE_OF;
+    // TODO(b/145259212): Should be `EXPECTED_BOOLEAN_VALUE_OF` (2) when compiling for dex.
+    int expectedBooleanValueOfCount = 6;
+    // TODO(b/145253152): Should be `EXPECTED_INTEGER_VALUE_OF` (2) when compiling for dex.
+    int expectedIntValueOfCount = 5;
+    // TODO(b/145253152): Should be `EXPECTED_LONG_VALUE_OF` (7) when compiling for dex.
+    int expectedLongValueOfCount = 10;
     test(
         result,
         expectedMaxCount,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanValueOfTest.java
new file mode 100644
index 0000000..85eb82b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanValueOfTest.java
@@ -0,0 +1,179 @@
+// Copyright (c) 2019, 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.library;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.google.common.base.Predicates.or;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BooleanValueOfTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public BooleanValueOfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(BooleanValueOfTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true", "false", "true", "false", "true", "false");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject testBooleanValueOfTrue =
+        testClassSubject.uniqueMethodWithName("testBooleanValueOfTrue");
+    assertThat(testBooleanValueOfTrue, isPresent());
+    assertTrue(
+        testBooleanValueOfTrue
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .map(invoke -> invoke.getMethod().name.toSourceString())
+            .noneMatch("booleanValue"::equals));
+    assertTrue(
+        testBooleanValueOfTrue
+            .streamInstructions()
+            .filter(InstructionSubject::isStaticGet)
+            .map(staticGet -> staticGet.getField().name.toSourceString())
+            .noneMatch("TRUE"::equals));
+
+    MethodSubject testBooleanValueOfFalse =
+        testClassSubject.uniqueMethodWithName("testBooleanValueOfFalse");
+    assertThat(testBooleanValueOfFalse, isPresent());
+    assertTrue(
+        testBooleanValueOfFalse
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .map(invoke -> invoke.getMethod().name.toSourceString())
+            .noneMatch("booleanValue"::equals));
+    assertTrue(
+        testBooleanValueOfFalse
+            .streamInstructions()
+            .filter(InstructionSubject::isStaticGet)
+            .map(staticGet -> staticGet.getField().name.toSourceString())
+            .noneMatch("FALSE"::equals));
+
+    MethodSubject testRoundTripTrueMethodSubject =
+        testClassSubject.uniqueMethodWithName("testRoundTripTrue");
+    assertThat(testRoundTripTrueMethodSubject, isPresent());
+    assertTrue(
+        testRoundTripTrueMethodSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .map(invoke -> invoke.getMethod().name.toSourceString())
+            .noneMatch(or("booleanValue"::equals, "valueOf"::equals)));
+
+    MethodSubject testRoundTripFalseMethodSubject =
+        testClassSubject.uniqueMethodWithName("testRoundTripFalse");
+    assertThat(testRoundTripFalseMethodSubject, isPresent());
+    assertTrue(
+        testRoundTripFalseMethodSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .map(invoke -> invoke.getMethod().name.toSourceString())
+            .noneMatch(or("booleanValue"::equals, "valueOf"::equals)));
+
+    MethodSubject testValueOfTrue = testClassSubject.uniqueMethodWithName("testValueOfTrue");
+    assertThat(testValueOfTrue, isPresent());
+    assertTrue(
+        testValueOfTrue
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .map(invoke -> invoke.getMethod().name.toSourceString())
+            .noneMatch("valueOf"::equals));
+    assertTrue(
+        testValueOfTrue
+            .streamInstructions()
+            .filter(InstructionSubject::isStaticGet)
+            .map(staticGet -> staticGet.getField().name.toSourceString())
+            .anyMatch("TRUE"::equals));
+
+    MethodSubject testValueOfFalse = testClassSubject.uniqueMethodWithName("testValueOfFalse");
+    assertThat(testValueOfFalse, isPresent());
+    assertTrue(
+        testValueOfFalse
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .map(invoke -> invoke.getMethod().name.toSourceString())
+            .noneMatch("valueOf"::equals));
+    assertTrue(
+        testValueOfFalse
+            .streamInstructions()
+            .filter(InstructionSubject::isStaticGet)
+            .map(staticGet -> staticGet.getField().name.toSourceString())
+            .anyMatch("FALSE"::equals));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testBooleanValueOfTrue();
+      testBooleanValueOfFalse();
+      testRoundTripTrue();
+      testRoundTripFalse();
+      testValueOfTrue();
+      testValueOfFalse();
+    }
+
+    @NeverInline
+    static void testBooleanValueOfTrue() {
+      System.out.println(Boolean.TRUE.booleanValue());
+    }
+
+    @NeverInline
+    static void testBooleanValueOfFalse() {
+      System.out.println(Boolean.FALSE.booleanValue());
+    }
+
+    @NeverInline
+    static void testRoundTripTrue() {
+      System.out.println(Boolean.valueOf(true).booleanValue());
+    }
+
+    @NeverInline
+    static void testRoundTripFalse() {
+      System.out.println(Boolean.valueOf(false).booleanValue());
+    }
+
+    @NeverInline
+    static void testValueOfTrue() {
+      System.out.println(Boolean.valueOf(true));
+    }
+
+    @NeverInline
+    static void testValueOfFalse() {
+      System.out.println(Boolean.valueOf(false));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index e83a89f..555b94c 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -58,6 +59,21 @@
     }
 
     @Override
+    public void replaceCurrentInstructionWithConstInt(
+        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+      throw new Unimplemented();
+    }
+
+    @Override
+    public void replaceCurrentInstructionWithStaticGet(
+        AppView<? extends AppInfoWithSubtyping> appView,
+        IRCode code,
+        DexField field,
+        Set<Value> affectedValues) {
+      throw new Unimplemented();
+    }
+
+    @Override
     public void replaceCurrentInstructionWithThrowNull(
         AppView<? extends AppInfoWithSubtyping> appView,
         IRCode code,