Merge commit '9a4ecac0' into dev-release
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 646a96a..d96a5a1 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,8 +163,8 @@
     }
 
     public boolean isShrinking() {
-       // Disable for release.
-       return false;
+      // Answers true if keep rules, even empty, are provided.
+      return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6c3efbe..1eb4ec9 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -27,12 +27,16 @@
    * @return Major version or -1 for an unreleased build.
    */
   public static int getMajorVersion() {
-    if (LABEL.equals("master")) {
+    return getMajorVersion(LABEL);
+  }
+
+  static int getMajorVersion(String label) {
+    if (label.equals("master")) {
       return -1;
     }
     int start = 0;
-    int end = LABEL.indexOf('.');
-    return Integer.parseInt(LABEL.substring(start, end));
+    int end = label.indexOf('.');
+    return Integer.parseInt(label.substring(start, end));
   }
 
   /**
@@ -41,12 +45,16 @@
    * @return Minor version or -1 for an unreleased build.
    */
   public static int getMinorVersion() {
-    if (LABEL.equals("master")) {
+    return getMinorVersion(LABEL);
+  }
+
+  static int getMinorVersion(String label) {
+    if (label.equals("master")) {
       return -1;
     }
-    int start = LABEL.indexOf('.') + 1;
-    int end = LABEL.indexOf('.', start);
-    return Integer.parseInt(LABEL.substring(start, end));
+    int start = label.indexOf('.') + 1;
+    int end = label.indexOf('.', start);
+    return Integer.parseInt(label.substring(start, end));
   }
 
   /**
@@ -55,13 +63,17 @@
    * @return Patch version or -1 for an unreleased build.
    */
   public static int getPatchVersion() {
-    if (LABEL.equals("master")) {
+    return getPatchVersion(LABEL);
+  }
+
+  static int getPatchVersion(String label) {
+    if (label.equals("master")) {
       return -1;
     }
-    int skip = LABEL.indexOf('.') + 1;
-    int start = LABEL.indexOf('.', skip) + 1;
-    int end = LABEL.indexOf('.', start);
-    return Integer.parseInt(LABEL.substring(start, end));
+    int skip = label.indexOf('.') + 1;
+    int start = label.indexOf('.', skip) + 1;
+    int end = label.indexOf('-', start);
+    return Integer.parseInt(label.substring(start, end != -1 ? end : label.length()));
   }
 
   /**
@@ -71,12 +83,16 @@
    *     unreleased build.
    */
   public static String getPreReleaseString() {
-    if (LABEL.equals("master")) {
+    return getPreReleaseString(LABEL);
+  }
+
+  static String getPreReleaseString(String label) {
+    if (label.equals("master")) {
       return null;
     }
-    int start = LABEL.indexOf('-') + 1;
+    int start = label.indexOf('-') + 1;
     if (start > 0) {
-      return LABEL.substring(start);
+      return label.substring(start);
     }
     return "";
   }
@@ -87,8 +103,10 @@
    * @return True if the build is not a release or if it is a development release.
    */
   public static boolean isDevelopmentVersion() {
-    return LABEL.equals("master")
-        || LABEL.endsWith("-dev")
-        || VersionProperties.INSTANCE.isEngineering();
+    return isDevelopmentVersion(LABEL, VersionProperties.INSTANCE.isEngineering());
+  }
+
+  static boolean isDevelopmentVersion(String label, boolean isEngineering) {
+    return label.equals("master") || label.endsWith("-dev") || isEngineering;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 72e372a..c340ae7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -626,6 +626,14 @@
     return accessFlags.isAbstract();
   }
 
+  public boolean isFinal() {
+    return accessFlags.isFinal();
+  }
+
+  public boolean isEffectivelyFinal(AppView<?> appView) {
+    return isFinal();
+  }
+
   public boolean isInterface() {
     return accessFlags.isInterface();
   }
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/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 92a4963..5a89dcf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -228,6 +229,26 @@
     return type.toSourceString();
   }
 
+  /**
+   * Returns true if this class is final, or it is a non-pinned program class with no instantiated
+   * subtypes.
+   */
+  @Override
+  public boolean isEffectivelyFinal(AppView<?> appView) {
+    if (isFinal()) {
+      return true;
+    }
+    if (appView.enableWholeProgramOptimizations()) {
+      assert appView.appInfo().hasLiveness();
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+      if (appInfo.isPinned(type)) {
+        return false;
+      }
+      return !appInfo.hasSubtypes(type) || !appInfo.isInstantiatedIndirectly(type);
+    }
+    return false;
+  }
+
   @Override
   public boolean isProgramClass() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 76dd396..4d8d00d 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -73,6 +73,7 @@
 import java.util.function.BiFunction;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ConstantDynamic;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -798,6 +799,8 @@
         instructions.add(
             new CfConstMethodHandle(
                 DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
+      } else if (cst instanceof ConstantDynamic) {
+        throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
       } else {
         throw new CompilationError("Unsupported constant: " + cst.toString());
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 4adc57e..3ad41d4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -89,7 +89,7 @@
   }
 
   @Override
-  public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
+  public ClassTypeLatticeElement getOrCreateVariant(Nullability nullability) {
     ClassTypeLatticeElement variant = variants.get(nullability);
     if (variant != null) {
       return variant;
@@ -116,6 +116,11 @@
   }
 
   @Override
+  public ClassTypeLatticeElement asMeetWithNotNull() {
+    return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
+  }
+
+  @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     builder.append(nullability);
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/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index b2273e9..d71b495 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -559,30 +559,13 @@
   }
 
   public int numberInstructions(int nextInstructionNumber) {
-    return numberInstructions(nextInstructionNumber, INSTRUCTION_NUMBER_DELTA);
-  }
-
-  public int numberInstructions(int nextInstructionNumber, int increment) {
     for (Instruction instruction : instructions) {
       instruction.setNumber(nextInstructionNumber);
-      nextInstructionNumber += increment;
+      nextInstructionNumber += INSTRUCTION_NUMBER_DELTA;
     }
     return nextInstructionNumber;
   }
 
-  public void clearInstructionNumbers() {
-    for (Instruction instruction : instructions) {
-      instruction.clearNumber();
-    }
-  }
-
-  public boolean hasNoInstructionNumbers() {
-    for (Instruction instruction : instructions) {
-      assert instruction.getNumber() == -1;
-    }
-    return true;
-  }
-
   public LinkedList<Instruction> getInstructions() {
     return instructions;
   }
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/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 805aad9..1a34adf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -763,7 +763,13 @@
 
   private boolean consistentMetadata() {
     for (Instruction instruction : instructions()) {
-      if (instruction.isCheckCast()) {
+      if (instruction.isAdd()) {
+        assert metadata.mayHaveAdd() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has an add";
+      } else if (instruction.isAnd()) {
+        assert metadata.mayHaveAnd() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has an and";
+      } else if (instruction.isCheckCast()) {
         assert metadata.mayHaveCheckCast()
             : "IR metadata should indicate that code has a check-cast";
       } else if (instruction.isConstNumber()) {
@@ -778,6 +784,9 @@
       } else if (instruction.isDexItemBasedConstString()) {
         assert metadata.mayHaveDexItemBasedConstString()
             : "IR metadata should indicate that code has a dex-item-based-const-string";
+      } else if (instruction.isDiv()) {
+        assert metadata.mayHaveDiv() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has a div";
       } else if (instruction.isInstanceGet()) {
         assert metadata.mayHaveInstanceGet()
             : "IR metadata should indicate that code has an instance-get";
@@ -808,9 +817,24 @@
       } else if (instruction.isInvokeVirtual()) {
         assert metadata.mayHaveInvokeVirtual()
             : "IR metadata should indicate that code has an invoke-virtual";
+      } else if (instruction.isOr()) {
+        assert metadata.mayHaveOr() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has an or";
       } else if (instruction.isMonitor()) {
         assert metadata.mayHaveMonitorInstruction()
             : "IR metadata should indicate that code has a monitor instruction";
+      } else if (instruction.isMul()) {
+        assert metadata.mayHaveMul() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has a mul";
+      } else if (instruction.isRem()) {
+        assert metadata.mayHaveRem() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has a rem";
+      } else if (instruction.isShl()) {
+        assert metadata.mayHaveShl() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has a shl";
+      } else if (instruction.isShr()) {
+        assert metadata.mayHaveShr() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has a shr";
       } else if (instruction.isStaticGet()) {
         assert metadata.mayHaveStaticGet()
             : "IR metadata should indicate that code has a static-get";
@@ -820,6 +844,15 @@
       } else if (instruction.isStringSwitch()) {
         assert metadata.mayHaveStringSwitch()
             : "IR metadata should indicate that code has a string-switch";
+      } else if (instruction.isSub()) {
+        assert metadata.mayHaveSub() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has a sub";
+      } else if (instruction.isUshr()) {
+        assert metadata.mayHaveUshr() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has an ushr";
+      } else if (instruction.isXor()) {
+        assert metadata.mayHaveXor() && metadata.mayHaveArithmeticOrLogicalBinop()
+            : "IR metadata should indicate that code has an xor";
       }
     }
     return true;
@@ -958,25 +991,6 @@
     return blocks;
   }
 
-  public void numberInstructionsPerBlock() {
-    for (BasicBlock block : blocks) {
-      block.numberInstructions(0, 1);
-    }
-  }
-
-  public void clearInstructionNumbers() {
-    for (BasicBlock block : blocks) {
-      block.clearInstructionNumbers();
-    }
-  }
-
-  public boolean hasNoInstructionNumbers() {
-    for (BasicBlock block : blocks) {
-      assert block.hasNoInstructionNumbers();
-    }
-    return true;
-  }
-
   public int numberRemainingInstructions() {
     for (Instruction instruction : instructions()) {
       if (instruction.getNumber() == -1) {
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/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index f6054e1..c4d55d8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -57,6 +57,14 @@
     second |= metadata.second;
   }
 
+  public boolean mayHaveAdd() {
+    return get(Opcodes.ADD);
+  }
+
+  public boolean mayHaveAnd() {
+    return get(Opcodes.AND);
+  }
+
   public boolean mayHaveCheckCast() {
     return get(Opcodes.CHECK_CAST);
   }
@@ -77,15 +85,19 @@
     return get(Opcodes.DEX_ITEM_BASED_CONST_STRING);
   }
 
+  public boolean mayHaveDiv() {
+    return get(Opcodes.DIV);
+  }
+
   public boolean mayHaveFieldGet() {
     return mayHaveInstanceGet() || mayHaveStaticGet();
   }
 
   public boolean mayHaveFieldInstruction() {
-    assert Opcodes.INSTANCE_GET <= 64;
-    assert Opcodes.INSTANCE_PUT <= 64;
-    assert Opcodes.STATIC_GET <= 64;
-    assert Opcodes.STATIC_PUT <= 64;
+    assert Opcodes.INSTANCE_GET < 64;
+    assert Opcodes.INSTANCE_PUT < 64;
+    assert Opcodes.STATIC_GET < 64;
+    assert Opcodes.STATIC_PUT < 64;
     long mask =
         (1L << Opcodes.INSTANCE_GET)
             | (1L << Opcodes.INSTANCE_PUT)
@@ -126,12 +138,12 @@
 
   @SuppressWarnings("ConstantConditions")
   public boolean mayHaveInvokeMethod() {
-    assert Opcodes.INVOKE_DIRECT <= 64;
-    assert Opcodes.INVOKE_INTERFACE <= 64;
-    assert Opcodes.INVOKE_POLYMORPHIC <= 64;
-    assert Opcodes.INVOKE_STATIC <= 64;
-    assert Opcodes.INVOKE_SUPER <= 64;
-    assert Opcodes.INVOKE_VIRTUAL <= 64;
+    assert Opcodes.INVOKE_DIRECT < 64;
+    assert Opcodes.INVOKE_INTERFACE < 64;
+    assert Opcodes.INVOKE_POLYMORPHIC < 64;
+    assert Opcodes.INVOKE_STATIC < 64;
+    assert Opcodes.INVOKE_SUPER < 64;
+    assert Opcodes.INVOKE_VIRTUAL < 64;
     long mask =
         (1L << Opcodes.INVOKE_DIRECT)
             | (1L << Opcodes.INVOKE_INTERFACE)
@@ -152,10 +164,10 @@
 
   @SuppressWarnings("ConstantConditions")
   public boolean mayHaveInvokeMethodWithReceiver() {
-    assert Opcodes.INVOKE_DIRECT <= 64;
-    assert Opcodes.INVOKE_INTERFACE <= 64;
-    assert Opcodes.INVOKE_SUPER <= 64;
-    assert Opcodes.INVOKE_VIRTUAL <= 64;
+    assert Opcodes.INVOKE_DIRECT < 64;
+    assert Opcodes.INVOKE_INTERFACE < 64;
+    assert Opcodes.INVOKE_SUPER < 64;
+    assert Opcodes.INVOKE_VIRTUAL < 64;
     long mask =
         (1L << Opcodes.INVOKE_DIRECT)
             | (1L << Opcodes.INVOKE_INTERFACE)
@@ -190,6 +202,26 @@
     return get(Opcodes.MONITOR);
   }
 
+  public boolean mayHaveMul() {
+    return get(Opcodes.MUL);
+  }
+
+  public boolean mayHaveOr() {
+    return get(Opcodes.OR);
+  }
+
+  public boolean mayHaveRem() {
+    return get(Opcodes.REM);
+  }
+
+  public boolean mayHaveShl() {
+    return get(Opcodes.SHL);
+  }
+
+  public boolean mayHaveShr() {
+    return get(Opcodes.SHR);
+  }
+
   public boolean mayHaveStaticGet() {
     return get(Opcodes.STATIC_GET);
   }
@@ -202,8 +234,58 @@
     return get(Opcodes.STRING_SWITCH);
   }
 
+  public boolean mayHaveSub() {
+    return get(Opcodes.SUB);
+  }
+
+  public boolean mayHaveUshr() {
+    return get(Opcodes.USHR);
+  }
+
+  public boolean mayHaveXor() {
+    return get(Opcodes.XOR);
+  }
+
   public boolean mayHaveArithmeticOrLogicalBinop() {
-    // TODO(b/7145202413): Implement this.
-    return true;
+    // ArithmeticBinop
+    assert Opcodes.ADD < 64;
+    assert Opcodes.DIV < 64;
+    assert Opcodes.MUL < 64;
+    assert Opcodes.REM < 64;
+    assert Opcodes.SUB < 64;
+    // LogicalBinop
+    assert Opcodes.AND < 64;
+    assert Opcodes.OR < 64;
+    assert Opcodes.SHL < 64;
+    assert Opcodes.SHR < 64;
+    assert Opcodes.USHR >= 64;
+    assert Opcodes.XOR >= 64;
+    long mask =
+        (1L << Opcodes.ADD)
+            | (1L << Opcodes.DIV)
+            | (1L << Opcodes.MUL)
+            | (1L << Opcodes.REM)
+            | (1L << Opcodes.SUB)
+            | (1L << Opcodes.AND)
+            | (1L << Opcodes.OR)
+            | (1L << Opcodes.SHL)
+            | (1L << Opcodes.SHR)
+            | (1L << Opcodes.USHR)
+            | (1L << Opcodes.XOR);
+    long other = (1L << (Opcodes.USHR - 64)) | (1L << (Opcodes.XOR - 64));
+    boolean result = isAnySetInFirst(mask) || isAnySetInSecond(other);
+    assert result
+        == (mayHaveAdd()
+            || mayHaveDiv()
+            || mayHaveMul()
+            || mayHaveRem()
+            || mayHaveSub()
+            || mayHaveAnd()
+            || mayHaveOr()
+            || mayHaveShl()
+            || mayHaveShr()
+            || mayHaveUshr()
+            || mayHaveXor());
+    return result;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 6893464..8deda3f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -268,6 +268,7 @@
   /**
    * Returns the basic block containing this instruction.
    */
+  @Override
   public BasicBlock getBlock() {
     assert block != null;
     return block;
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/InstructionOrPhi.java b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
index e9a5347..144bbdb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
@@ -21,4 +21,6 @@
   default Phi asPhi() {
     return null;
   }
+
+  BasicBlock getBlock();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index aea5a70..87b93a4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-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.DexItemFactory;
@@ -17,6 +16,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 
 public abstract class InvokeMethodWithReceiver extends InvokeMethod {
@@ -74,15 +74,15 @@
     TypeLatticeElement receiverType = receiver.getTypeLattice();
     assert receiverType.isPreciseType();
 
-    if (appView.appInfo().hasSubtyping()) {
-      AppView<? extends AppInfoWithSubtyping> appViewWithSubtyping = appView.withSubtyping();
+    if (appView.appInfo().hasLiveness()) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       ClassTypeLatticeElement receiverLowerBoundType =
-          receiver.getDynamicLowerBoundType(appViewWithSubtyping);
+          receiver.getDynamicLowerBoundType(appViewWithLiveness);
       if (receiverLowerBoundType != null) {
         DexType refinedReceiverType =
-            TypeAnalysis.getRefinedReceiverType(appViewWithSubtyping, this);
+            TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
         assert receiverLowerBoundType.getClassType() == refinedReceiverType
-            || receiverLowerBoundType.isBasedOnMissingClass(appViewWithSubtyping);
+            || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness);
       }
     }
 
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/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 3252d67..2f0911d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -67,6 +67,7 @@
     return this;
   }
 
+  @Override
   public BasicBlock getBlock() {
     return block;
   }
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 f9ca31d..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,13 +14,13 @@
 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;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SetUtils;
@@ -1114,21 +1116,22 @@
     return lattice;
   }
 
-  public ClassTypeLatticeElement getDynamicLowerBoundType(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+  public ClassTypeLatticeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
     Value root = getAliasedValue();
     if (root.isPhi()) {
       return null;
     }
+
     Instruction definition = root.definition;
     if (definition.isNewInstance()) {
       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;
     }
+
     // Try to find an alias of the receiver, which is defined by an instruction of the type
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
@@ -1136,9 +1139,20 @@
       ClassTypeLatticeElement lattice =
           aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicLowerBoundType();
       return lattice != null && typeLattice.isDefinitelyNotNull() && lattice.isNullable()
-          ? lattice.asMeetWithNotNull().asClassTypeLatticeElement()
+          ? lattice.asMeetWithNotNull()
           : lattice;
     }
+
+    // If it is a final or effectively-final class type, then we know the lower bound.
+    if (getTypeLattice().isClassType()) {
+      ClassTypeLatticeElement classType = getTypeLattice().asClassTypeLatticeElement();
+      DexType type = classType.getClassType();
+      DexClass clazz = appView.definitionFor(type);
+      if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+        return classType;
+      }
+    }
+
     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/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index d48d1f2..3a78425 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InstructionOrPhi;
 import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeDirect;
@@ -1646,8 +1647,6 @@
       return;
     }
 
-    code.numberInstructionsPerBlock();
-
     for (BasicBlock block : code.blocks) {
       InstructionListIterator instructionIterator = block.listIterator(code);
       // Collect all the non constant in values for binop/lit8 or binop/lit16 instructions.
@@ -1669,26 +1668,32 @@
       Reference2IntMap<Value> lastUseOfBinopsWithLit8OrLit16NonConstantValues =
           new Reference2IntOpenHashMap<>();
       lastUseOfBinopsWithLit8OrLit16NonConstantValues.defaultReturnValue(-1);
+      int currentInstructionNumber = block.getInstructions().size();
       while (instructionIterator.hasPrevious()) {
         Instruction currentInstruction = instructionIterator.previous();
-        for (Value value : Iterables.concat(currentInstruction.inValues(), currentInstruction.getDebugValues())) {
+        currentInstructionNumber--;
+        for (Value value :
+            Iterables.concat(currentInstruction.inValues(), currentInstruction.getDebugValues())) {
           if (!binopsWithLit8OrLit16NonConstantValues.contains(value)) {
             continue;
           }
           if (!lastUseOfBinopsWithLit8OrLit16NonConstantValues.containsKey(value)) {
-            lastUseOfBinopsWithLit8OrLit16NonConstantValues.put(
-                value, currentInstruction.getNumber());
+            lastUseOfBinopsWithLit8OrLit16NonConstantValues.put(value, currentInstructionNumber);
           }
         }
       }
       // Do the transformation except if the binop can use the binop/2addr format.
+      currentInstructionNumber--;
+      assert currentInstructionNumber == -1;
       while (instructionIterator.hasNext()) {
         Instruction currentInstruction = instructionIterator.next();
+        currentInstructionNumber++;
         if (!isBinopWithLit8OrLit16(currentInstruction)) {
           continue;
         }
         Binop binop = currentInstruction.asBinop();
-        if (!canBe2AddrInstruction(binop, lastUseOfBinopsWithLit8OrLit16NonConstantValues)) {
+        if (!canBe2AddrInstruction(
+            binop, currentInstructionNumber, lastUseOfBinopsWithLit8OrLit16NonConstantValues)) {
           Value constValue = binopWithLit8OrLit16Constant(currentInstruction);
           if (constValue.numberOfAllUsers() > 1) {
             // No need to do the transformation if the const value is already used only one time.
@@ -1706,9 +1711,6 @@
       }
     }
 
-    code.clearInstructionNumbers();
-    assert code.hasNoInstructionNumbers();
-
     assert code.isConsistentSSA();
   }
 
@@ -1751,55 +1753,46 @@
   }
 
   /**
-   * Estimate if a binary operation can be a 2addr form or not. It can be a 2addr form when an
+   * Estimate if a binary operation can be a binop/2addr form or not. It can be a 2addr form when an
    * argument is no longer needed after the binary operation and can be overwritten. That is
    * definitely the case if there is no path between the binary operation and all other usages.
    */
   private static boolean canBe2AddrInstruction(
-      Binop binop, Reference2IntMap<Value> lastUseOfRelevantValue) {
+      Binop binop, int binopInstructionNumber, Reference2IntMap<Value> lastUseOfRelevantValue) {
     Value value = binopWithLit8OrLit16NonConstant(binop);
     assert value != null;
-    int number = lastUseOfRelevantValue.getInt(value);
-    if (number > 0 && number > binop.getNumber()) {
+    int lastUseInstructionNumber = lastUseOfRelevantValue.getInt(value);
+    // The binop instruction is a user, so there is always a last use in the block.
+    assert lastUseInstructionNumber != -1;
+    if (lastUseInstructionNumber > binopInstructionNumber) {
       return false;
     }
-    Iterable<Instruction> users =
+
+    Set<BasicBlock> noPathTo = Sets.newIdentityHashSet();
+    BasicBlock binopBlock = binop.getBlock();
+    Iterable<InstructionOrPhi> users =
         value.debugUsers() != null
-            ? Iterables.concat(value.uniqueUsers(), value.debugUsers())
-            : value.uniqueUsers();
-
-    for (Instruction user : users) {
-      if (hasPath(binop, user)) {
+            ? Iterables.concat(value.uniqueUsers(), value.debugUsers(), value.uniquePhiUsers())
+            : Iterables.concat(value.uniqueUsers(), value.uniquePhiUsers());
+    for (InstructionOrPhi user : users) {
+      BasicBlock userBlock = user.getBlock();
+      if (userBlock == binopBlock) {
+        // All users in the current block are either before the binop instruction or the
+        // binop instruction itself.
+        continue;
+      }
+      if (noPathTo.contains(userBlock)) {
+        continue;
+      }
+      if (binopBlock.hasPathTo(userBlock)) {
         return false;
       }
-    }
-
-    for (Phi user : value.uniquePhiUsers()) {
-      if (binop.getBlock().hasPathTo(user.getBlock())) {
-        return false;
-      }
+      noPathTo.add(userBlock);
     }
 
     return true;
   }
 
-  /**
-   * Return true if there is a path between {@code source} instruction and {@code target}
-   * instruction.
-   */
-  private static boolean hasPath(Instruction source, Instruction target) {
-    BasicBlock sourceBlock = source.getBlock();
-    BasicBlock targetBlock = target.getBlock();
-    if (sourceBlock == targetBlock) {
-      // Instructions must be numbered when getting here.
-      assert source.getNumber() != -1;
-      assert target.getNumber() != -1;
-      return source.getNumber() < target.getNumber();
-    }
-
-    return source.getBlock().hasPathTo(targetBlock);
-  }
-
   public void shortenLiveRanges(IRCode code) {
     // Currently, we are only shortening the live range of ConstNumbers in the entry block
     // and ConstStrings with one user.
@@ -3019,7 +3012,7 @@
                 newInstruction.setBlock(phiBlock);
                 // The xor is replacing a phi so it does not have an actual position.
                 newInstruction.setPosition(phiPosition);
-                phiBlock.getInstructions().add(insertIndex, newInstruction);
+                phiBlock.listIterator(code, insertIndex).add(newInstruction);
                 deadPhis++;
               }
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index d9e51ea..06c5dfe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -76,7 +76,8 @@
 
         MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
         if (optimizationInfo.returnsArgument()) {
-          assert optimizationInfo.getDynamicLowerBoundType() == null;
+          // Don't insert an assume-instruction since we will replace all usages of the out-value by
+          // the corresponding argument.
           continue;
         }
 
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/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 36112c9..0ba6b03 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -944,8 +944,8 @@
         return false;
       }
 
-      // TODO(b/124842076) Extend this check to use checksNullReceiverBeforeAnySideEffect.
-      if (receiver.getTypeLattice().isNullable()) {
+      // A definitely null receiver will throw an error on call site.
+      if (receiver.getTypeLattice().nullability().isDefinitelyNull()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index ae5e066..041616c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -13,9 +13,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import com.google.common.io.BaseEncoding;
@@ -163,22 +163,26 @@
 
   protected abstract String getGroupSuffix();
 
-  final DexProgramClass synthesizeClass(InternalOptions options) {
+  final DexProgramClass synthesizeClass(
+      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback) {
     assert classType == null;
     assert verifyLambdaIds(true);
     List<LambdaInfo> lambdas = Lists.newArrayList(this.lambdas.values());
     classType =
-        options.itemFactory.createType(
-            "L"
-                + getTypePackage()
-                + "-$$LambdaGroup$"
-                + getGroupSuffix()
-                + createHash(lambdas)
-                + ";");
-    return getBuilder(options.itemFactory).synthesizeClass(options);
+        appView
+            .dexItemFactory()
+            .createType(
+                "L"
+                    + getTypePackage()
+                    + "-$$LambdaGroup$"
+                    + getGroupSuffix()
+                    + createHash(lambdas)
+                    + ";");
+    return getBuilder(appView.dexItemFactory()).synthesizeClass(appView, feedback);
   }
 
-  protected abstract LambdaGroupClassBuilder getBuilder(DexItemFactory factory);
+  protected abstract LambdaGroupClassBuilder<? extends LambdaGroup> getBuilder(
+      DexItemFactory factory);
 
   private String createHash(List<LambdaInfo> lambdas) {
     try {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index e569714..2c4315c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.lambda;
 
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -14,8 +16,8 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
 import java.util.List;
 
@@ -32,7 +34,8 @@
     this.origin = origin;
   }
 
-  public final DexProgramClass synthesizeClass(InternalOptions options) {
+  public final DexProgramClass synthesizeClass(
+      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback) {
     DexType groupClassType = group.getGroupClassType();
     DexType superClassType = getSuperClassType();
     DexProgramClass programClass =
@@ -49,7 +52,7 @@
             buildEnclosingMethodAttribute(),
             buildInnerClasses(),
             buildAnnotations(),
-            buildStaticFields(),
+            buildStaticFields(appView, feedback),
             buildInstanceFields(),
             buildDirectMethods(),
             buildVirtualMethods(),
@@ -75,7 +78,8 @@
 
   protected abstract DexEncodedField[] buildInstanceFields();
 
-  protected abstract DexEncodedField[] buildStaticFields();
+  protected abstract DexEncodedField[] buildStaticFields(
+      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback);
 
   protected abstract DexTypeList buildInterfaces();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 3bb09ce..9981290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -326,7 +326,7 @@
 
     // Remove invalidated lambdas, compact groups to ensure
     // sequential lambda ids, create group lambda classes.
-    BiMap<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
+    BiMap<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups(feedback);
 
     // Fixup optimization info to ensure that the optimization info does not refer to any merged
     // lambdas.
@@ -388,7 +388,7 @@
     ThreadUtils.awaitFutures(futures);
   }
 
-  private BiMap<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
+  private BiMap<LambdaGroup, DexProgramClass> finalizeLambdaGroups(OptimizationFeedback feedback) {
     for (DexType lambda : invalidatedLambdas) {
       LambdaGroup group = lambdas.get(lambda);
       assert group != null;
@@ -405,7 +405,7 @@
     for (LambdaGroup group : groups.values()) {
       assert !group.isTrivial() : "No trivial group is expected here.";
       group.compact();
-      DexProgramClass lambdaGroupClass = group.synthesizeClass(appView.options());
+      DexProgramClass lambdaGroupClass = group.synthesizeClass(appView, feedback);
       result.put(group, lambdaGroupClass);
     }
     return result;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
index db9f04e..07c1b08 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -107,7 +106,7 @@
   }
 
   @Override
-  protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
+  protected ClassBuilder getBuilder(DexItemFactory factory) {
     return new ClassBuilder(factory, "java-style lambda group");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index 1595e8e..9e07c44 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -113,7 +112,7 @@
   }
 
   @Override
-  protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
+  protected ClassBuilder getBuilder(DexItemFactory factory) {
     return new ClassBuilder(factory, "kotlin-style lambda group");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 15d4160..74a5236 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -4,11 +4,16 @@
 
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -20,7 +25,9 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
@@ -212,18 +219,30 @@
   }
 
   @Override
-  protected DexEncodedField[] buildStaticFields() {
+  protected DexEncodedField[] buildStaticFields(
+      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback) {
     if (!group.isStateless()) {
       return DexEncodedField.EMPTY_ARRAY;
     }
     // One field for each singleton lambda in the group.
     List<DexEncodedField> result = new ArrayList<>(group.size());
-    group.forEachLambda(info -> {
-      if (group.isSingletonLambda(info.clazz.type)) {
-        result.add(new DexEncodedField(group.getSingletonInstanceField(factory, info.id),
-            SINGLETON_FIELD_FLAGS, DexAnnotationSet.empty(), DexValueNull.NULL));
-      }
-    });
+    group.forEachLambda(
+        info -> {
+          if (group.isSingletonLambda(info.clazz.type)) {
+            DexField field = group.getSingletonInstanceField(factory, info.id);
+            DexEncodedField encodedField =
+                new DexEncodedField(
+                    field, SINGLETON_FIELD_FLAGS, DexAnnotationSet.empty(), DexValueNull.NULL);
+            result.add(encodedField);
+
+            // Record that the field is definitely not null. It is guaranteed to be assigned in the
+            // class initializer of the enclosing class before it is read.
+            ClassTypeLatticeElement exactType =
+                ClassTypeLatticeElement.create(field.type, definitelyNotNull(), appView);
+            feedback.markFieldHasDynamicLowerBoundType(encodedField, exactType);
+            feedback.markFieldHasDynamicUpperBoundType(encodedField, exactType);
+          }
+        });
     assert result.isEmpty() == !group.hasAnySingletons();
     return result.toArray(DexEncodedField.EMPTY_ARRAY);
   }
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/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index a467cb2..12992ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -94,7 +94,9 @@
                 .add(this::insertAssumeInstructions)
                 .add(collectOptimizationInfo(feedback)));
 
-    // Enqueue instance methods to be staticized (only remove references to 'this').
+    // Enqueue instance methods to be staticized (only remove references to 'this'). Intentionally
+    // not collection optimization info for these methods, since they will be reprocessed again
+    // below once staticized.
     enqueueMethodsWithCodeOptimizations(
         methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));
 
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index 612be2c6..88f89c3 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.objectweb.asm.ConstantDynamic;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -118,6 +120,8 @@
       updateConstraint(inliningConstraints.forConstClass(type, invocationContext));
     } else if (cst instanceof Handle) {
       updateConstraint(inliningConstraints.forConstMethodHandle());
+    } else if (cst instanceof ConstantDynamic) {
+      throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
     } else {
       updateConstraint(inliningConstraints.forConstInstruction());
     }
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 991b861..fc9ea8b 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import org.objectweb.asm.ConstantDynamic;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -60,6 +62,8 @@
       } else {
         registry.registerConstClass(application.getType((Type) cst));
       }
+    } else if (cst instanceof ConstantDynamic) {
+      throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
     } else if (cst instanceof Handle) {
       DexMethodHandle handle = DexMethodHandle.fromAsmHandle((Handle) cst, application, clazz);
       registry.registerMethodHandle(
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index c008660..6347853 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -23,23 +23,20 @@
 
   // Enum describing the possible/supported CF runtimes.
   public enum CfVm {
-    JDK8("jdk8"),
-    JDK9("jdk9"),
-    JDK11("jdk11");
+    JDK8("jdk8", 52),
+    JDK9("jdk9", 53),
+    JDK11("jdk11", 55);
 
     private final String name;
+    private final int classfileVersion;
 
-    public static CfVm fromName(String v) {
-      for (CfVm value : CfVm.values()) {
-        if (value.name.equals(v)) {
-          return value;
-        }
-      }
-      return null;
+    CfVm(String name, int classfileVersion) {
+      this.name = name;
+      this.classfileVersion = classfileVersion;
     }
 
-    CfVm(String name) {
-      this.name = name;
+    public int getClassfileVersion() {
+      return classfileVersion;
     }
 
     public static CfVm first() {
@@ -58,10 +55,6 @@
       return this.ordinal() <= other.ordinal();
     }
 
-    public boolean hasModularRuntime() {
-      return this != JDK8;
-    }
-
     @Override
     public String toString() {
       return name;
diff --git a/src/test/java/com/android/tools/r8/VersionTests.java b/src/test/java/com/android/tools/r8/VersionTests.java
index ba5ee78..dffaa2b 100644
--- a/src/test/java/com/android/tools/r8/VersionTests.java
+++ b/src/test/java/com/android/tools/r8/VersionTests.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.Version.LABEL;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -62,4 +63,30 @@
   public void testDevelopmentPredicate() {
     assertEquals(LABEL.equals("master") || LABEL.contains("-dev"), Version.isDevelopmentVersion());
   }
+
+  @Test
+  public void testLabelParsing() {
+    assertEquals(-1, Version.getMajorVersion("master"));
+    assertEquals(-1, Version.getMinorVersion("master"));
+    assertEquals(-1, Version.getPatchVersion("master"));
+    assertNull(Version.getPreReleaseString("master"));
+    // 'master' is checked before 'isEngineering'.
+    assertTrue(Version.isDevelopmentVersion("master", false));
+    assertTrue(Version.isDevelopmentVersion("master", true));
+
+    assertEquals(1, Version.getMajorVersion("1.2.3-dev"));
+    assertEquals(2, Version.getMinorVersion("1.2.3-dev"));
+    assertEquals(3, Version.getPatchVersion("1.2.3-dev"));
+    assertEquals("dev", Version.getPreReleaseString("1.2.3-dev"));
+    // '-dev' suffix is checked before 'isEngineering'.
+    assertTrue(Version.isDevelopmentVersion("1.2.3-dev", false));
+    assertTrue(Version.isDevelopmentVersion("1.2.3-dev", true));
+
+    assertEquals(1, Version.getMajorVersion("1.2.3"));
+    assertEquals(2, Version.getMinorVersion("1.2.3"));
+    assertEquals(3, Version.getPatchVersion("1.2.3"));
+    assertEquals("", Version.getPreReleaseString("1.2.3"));
+    assertFalse(Version.isDevelopmentVersion("1.2.3", false));
+    assertTrue(Version.isDevelopmentVersion("1.2.3", true));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java b/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java
new file mode 100644
index 0000000..81f50fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java
@@ -0,0 +1,107 @@
+// 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.cf;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ConstantDynamic;
+import org.objectweb.asm.Handle;
+
+@RunWith(Parameterized.class)
+public class ConstantDynamicHolderTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  final TestParameters parameters;
+
+  public ConstantDynamicHolderTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("null");
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClassFileData(getTransformedMain())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertErrorMessageThatMatches(
+                    containsString("Unsupported dynamic constant")));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedMain())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertErrorMessageThatMatches(
+                    containsString("Unsupported dynamic constant")));
+  }
+
+  private byte[] getTransformedMain() throws IOException {
+    return transformer(Main.class)
+        .setMinVersion(CfVm.JDK11)
+        .transformLdcInsnInMethod(
+            "main",
+            (value, continuation) -> {
+              assertEquals("replaced by dynamic null constant", value);
+              continuation.apply(getDynamicConstant());
+            })
+        .transform();
+  }
+
+  private ConstantDynamic getDynamicConstant() {
+    return new ConstantDynamic(
+        "dynamicnull",
+        "Ljava/lang/String;",
+        new Handle(
+            H_INVOKESTATIC,
+            "java/lang/invoke/ConstantBootstraps",
+            "nullConstant",
+            "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)"
+                + "Ljava/lang/Object;",
+            false));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("replaced by dynamic null constant");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
index 62e1bf9..874d4ad 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
@@ -48,7 +48,8 @@
     return buildParameters(
         BooleanUtils.values(),
         getTestParameters()
-            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            // TODO(b/145281519): Should be Version.V5_1_1.
+            .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
             .withAllApiLevels()
             .build());
   }
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,
diff --git a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
similarity index 79%
copy from src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
copy to src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 786177c..63c6161 100644
--- a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -1,7 +1,7 @@
 // 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.graph.access;
+package com.android.tools.r8.resolution.access;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
 import static org.hamcrest.core.StringContains.containsString;
@@ -27,8 +27,9 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class NestMethodAccessTest extends TestBase {
-  static final String EXPECTED = StringUtils.lines("A::bar", "A::baz");
+public class NestStaticMethodAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar");
 
   private final TestParameters parameters;
   private final boolean inSameNest;
@@ -44,7 +45,7 @@
         BooleanUtils.values());
   }
 
-  public NestMethodAccessTest(TestParameters parameters, boolean inSameNest) {
+  public NestStaticMethodAccessTest(TestParameters parameters, boolean inSameNest) {
     this.parameters = parameters;
     this.inSameNest = inSameNest;
   }
@@ -55,10 +56,7 @@
 
   public Collection<byte[]> getTransformedClasses() throws Exception {
     return ImmutableList.of(
-        withNest(A.class)
-            .setPrivate(A.class.getDeclaredMethod("bar"))
-            .setPrivate(A.class.getDeclaredMethod("baz"))
-            .transform(),
+        withNest(A.class).setPrivate(A.class.getDeclaredMethod("bar")).transform(),
         withNest(B.class).transform());
   }
 
@@ -96,17 +94,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .disassemble()
-        .apply(
-            result -> {
-              if (parameters.isDexRuntime() && inSameNest) {
-                // TODO(b/145187969): R8 incorrectly compiles the nest based access away.
-                result.assertFailureWithErrorThatMatches(
-                    containsString(NullPointerException.class.getName()));
-              } else {
-                checkExpectedResult(result);
-              }
-            });
+        .apply(this::checkExpectedResult);
   }
 
   private void checkExpectedResult(TestRunResult<?> result) {
@@ -127,21 +115,15 @@
   }
 
   static class A {
-    /* will be private */ void bar() {
+    /* will be private */ static void bar() {
       System.out.println("A::bar");
     }
-
-    /* will be private */ static void baz() {
-      System.out.println("A::baz");
-    }
   }
 
   static class B {
     public void foo() {
-      // Virtual invoke to private method.
-      new A().bar();
       // Static invoke to private method.
-      A.baz();
+      A.bar();
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
similarity index 76%
copy from src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
copy to src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index 786177c..a487f40 100644
--- a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -1,7 +1,7 @@
 // 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.graph.access;
+package com.android.tools.r8.resolution.access;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
 import static org.hamcrest.core.StringContains.containsString;
@@ -27,8 +27,9 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class NestMethodAccessTest extends TestBase {
-  static final String EXPECTED = StringUtils.lines("A::bar", "A::baz");
+public class NestStaticMethodAccessWithIntermediateClassTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar");
 
   private final TestParameters parameters;
   private final boolean inSameNest;
@@ -44,7 +45,8 @@
         BooleanUtils.values());
   }
 
-  public NestMethodAccessTest(TestParameters parameters, boolean inSameNest) {
+  public NestStaticMethodAccessWithIntermediateClassTest(
+      TestParameters parameters, boolean inSameNest) {
     this.parameters = parameters;
     this.inSameNest = inSameNest;
   }
@@ -55,11 +57,9 @@
 
   public Collection<byte[]> getTransformedClasses() throws Exception {
     return ImmutableList.of(
-        withNest(A.class)
-            .setPrivate(A.class.getDeclaredMethod("bar"))
-            .setPrivate(A.class.getDeclaredMethod("baz"))
-            .transform(),
-        withNest(B.class).transform());
+        withNest(A.class).setPrivate(A.class.getDeclaredMethod("bar")).transform(),
+        withNest(B.class).transform(),
+        withNest(C.class).transform());
   }
 
   @Test
@@ -96,13 +96,11 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .disassemble()
         .apply(
             result -> {
-              if (parameters.isDexRuntime() && inSameNest) {
-                // TODO(b/145187969): R8 incorrectly compiles the nest based access away.
-                result.assertFailureWithErrorThatMatches(
-                    containsString(NullPointerException.class.getName()));
+              if (inSameNest) {
+                // TODO(b/145187969): R8 incorrectly compiles out the incorrect access.
+                result.assertSuccessWithOutput(EXPECTED);
               } else {
                 checkExpectedResult(result);
               }
@@ -110,8 +108,9 @@
   }
 
   private void checkExpectedResult(TestRunResult<?> result) {
-    if (inSameNest) {
-      result.assertSuccessWithOutput(EXPECTED);
+    if (inSameNest && parameters.isCfRuntime()) {
+      // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
+      result.assertFailureWithErrorThatMatches(containsString(NoSuchMethodError.class.getName()));
     } else {
       result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
     }
@@ -119,35 +118,31 @@
 
   private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
     if (inSameNest) {
-      // If in the same nest make A host and B a member.
-      return transformer(clazz).setNest(A.class, B.class);
+      // If in the same nest make A host with B and C as members.
+      return transformer(clazz).setNest(A.class, B.class, C.class);
     }
     // Otherwise, set the class to be its own host and no additional members.
     return transformer(clazz).setNest(clazz);
   }
 
   static class A {
-    /* will be private */ void bar() {
+    /* will be private */ static void bar() {
       System.out.println("A::bar");
     }
-
-    /* will be private */ static void baz() {
-      System.out.println("A::baz");
-    }
   }
 
-  static class B {
+  static class B extends A {}
+
+  static class C {
     public void foo() {
-      // Virtual invoke to private method.
-      new A().bar();
       // Static invoke to private method.
-      A.baz();
+      B.bar();
     }
   }
 
   static class Main {
     public static void main(String[] args) {
-      new B().foo();
+      new C().foo();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
rename to src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 786177c..59267b2 100644
--- a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -1,7 +1,7 @@
 // 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.graph.access;
+package com.android.tools.r8.resolution.access;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
 import static org.hamcrest.core.StringContains.containsString;
@@ -27,8 +27,9 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class NestMethodAccessTest extends TestBase {
-  static final String EXPECTED = StringUtils.lines("A::bar", "A::baz");
+public class NestVirtualMethodAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar");
 
   private final TestParameters parameters;
   private final boolean inSameNest;
@@ -44,7 +45,7 @@
         BooleanUtils.values());
   }
 
-  public NestMethodAccessTest(TestParameters parameters, boolean inSameNest) {
+  public NestVirtualMethodAccessTest(TestParameters parameters, boolean inSameNest) {
     this.parameters = parameters;
     this.inSameNest = inSameNest;
   }
@@ -57,7 +58,6 @@
     return ImmutableList.of(
         withNest(A.class)
             .setPrivate(A.class.getDeclaredMethod("bar"))
-            .setPrivate(A.class.getDeclaredMethod("baz"))
             .transform(),
         withNest(B.class).transform());
   }
@@ -96,7 +96,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .disassemble()
         .apply(
             result -> {
               if (parameters.isDexRuntime() && inSameNest) {
@@ -130,18 +129,12 @@
     /* will be private */ void bar() {
       System.out.println("A::bar");
     }
-
-    /* will be private */ static void baz() {
-      System.out.println("A::baz");
-    }
   }
 
   static class B {
     public void foo() {
       // Virtual invoke to private method.
       new A().bar();
-      // Static invoke to private method.
-      A.baz();
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
similarity index 67%
copy from src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
copy to src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 786177c..68cc59e 100644
--- a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -1,12 +1,15 @@
 // 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.graph.access;
+package com.android.tools.r8.resolution.access;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
@@ -27,8 +30,9 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class NestMethodAccessTest extends TestBase {
-  static final String EXPECTED = StringUtils.lines("A::bar", "A::baz");
+public class NestVirtualMethodAccessWithIntermediateClassTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar");
 
   private final TestParameters parameters;
   private final boolean inSameNest;
@@ -44,7 +48,8 @@
         BooleanUtils.values());
   }
 
-  public NestMethodAccessTest(TestParameters parameters, boolean inSameNest) {
+  public NestVirtualMethodAccessWithIntermediateClassTest(
+      TestParameters parameters, boolean inSameNest) {
     this.parameters = parameters;
     this.inSameNest = inSameNest;
   }
@@ -55,11 +60,9 @@
 
   public Collection<byte[]> getTransformedClasses() throws Exception {
     return ImmutableList.of(
-        withNest(A.class)
-            .setPrivate(A.class.getDeclaredMethod("bar"))
-            .setPrivate(A.class.getDeclaredMethod("baz"))
-            .transform(),
-        withNest(B.class).transform());
+        withNest(A.class).setPrivate(A.class.getDeclaredMethod("bar")).transform(),
+        withNest(B.class).transform(),
+        withNest(C.class).transform());
   }
 
   @Test
@@ -90,19 +93,31 @@
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(getClasses())
-        .addProgramClassFileData(getTransformedClasses())
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Main.class)
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(getClasses())
+            .addProgramClassFileData(getTransformedClasses())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Main.class);
+    // TODO(b/145196085): R8 fails compilation when the classes are in the same nest.
+    if (inSameNest) {
+      if (parameters.isDexRuntime()) {
+        try {
+          builder.compile();
+        } catch (CompilationFailedException e) {
+          // Expected failure.
+          return;
+        }
+        fail("Expected failure: b/145196085");
+      }
+    }
+    builder
         .run(parameters.getRuntime(), Main.class)
-        .disassemble()
         .apply(
             result -> {
-              if (parameters.isDexRuntime() && inSameNest) {
-                // TODO(b/145187969): R8 incorrectly compiles the nest based access away.
-                result.assertFailureWithErrorThatMatches(
-                    containsString(NullPointerException.class.getName()));
+              if (inSameNest) {
+                // TODO(b/145187969): R8 compiles out the errors when in the same nest.
+                result.assertSuccessWithOutput(EXPECTED);
               } else {
                 checkExpectedResult(result);
               }
@@ -110,8 +125,9 @@
   }
 
   private void checkExpectedResult(TestRunResult<?> result) {
-    if (inSameNest) {
-      result.assertSuccessWithOutput(EXPECTED);
+    if (inSameNest && parameters.isCfRuntime()) {
+      // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
+      result.assertFailureWithErrorThatMatches(containsString(NoSuchMethodError.class.getName()));
     } else {
       result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
     }
@@ -119,8 +135,8 @@
 
   private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
     if (inSameNest) {
-      // If in the same nest make A host and B a member.
-      return transformer(clazz).setNest(A.class, B.class);
+      // If in the same nest make A host with B and C as members.
+      return transformer(clazz).setNest(A.class, B.class, C.class);
     }
     // Otherwise, set the class to be its own host and no additional members.
     return transformer(clazz).setNest(clazz);
@@ -130,24 +146,20 @@
     /* will be private */ void bar() {
       System.out.println("A::bar");
     }
-
-    /* will be private */ static void baz() {
-      System.out.println("A::baz");
-    }
   }
 
-  static class B {
+  static class B extends A {}
+
+  static class C {
     public void foo() {
       // Virtual invoke to private method.
-      new A().bar();
-      // Static invoke to private method.
-      A.baz();
+      new B().bar();
     }
   }
 
   static class Main {
     public static void main(String[] args) {
-      new B().foo();
+      new C().foo();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index d0d40bb..09faf46 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -5,6 +5,7 @@
 
 import static org.objectweb.asm.Opcodes.ASM7;
 
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -27,8 +28,6 @@
 
 public class ClassFileTransformer {
 
-  private static final int NEST_SUPPORTED_VERSION = 55;
-
   /**
    * Basic algorithm for transforming the content of a class file.
    *
@@ -166,6 +165,10 @@
         });
   }
 
+  public ClassFileTransformer setMinVersion(CfVm jdk) {
+    return setMinVersion(jdk.getClassfileVersion());
+  }
+
   public ClassFileTransformer setMinVersion(int minVersion) {
     return addClassTransformer(
         new ClassTransformer() {
@@ -185,7 +188,7 @@
 
   public ClassFileTransformer setNest(Class<?> host, Class<?>... members) {
     assert !Arrays.asList(members).contains(host);
-    return setMinVersion(NEST_SUPPORTED_VERSION)
+    return setMinVersion(CfVm.JDK11)
         .addClassTransformer(
             new ClassTransformer() {
 
@@ -294,4 +297,31 @@
           }
         });
   }
+
+  /** Abstraction of the MethodVisitor.visitLdcInsn method with its continuation. */
+  @FunctionalInterface
+  public interface LdcInsnTransform {
+    void visitLdcInsn(Object value, LdcInsnTransformContinuation continuation);
+  }
+
+  /** Continuation for transforming a method. Will continue with the super visitor if called. */
+  @FunctionalInterface
+  public interface LdcInsnTransformContinuation {
+    void apply(Object value);
+  }
+
+  public ClassFileTransformer transformLdcInsnInMethod(
+      String methodName, LdcInsnTransform transform) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitLdcInsn(Object value) {
+            if (getContext().method.getMethodName().equals(methodName)) {
+              transform.visitLdcInsn(value, super::visitLdcInsn);
+            } else {
+              super.visitLdcInsn(value);
+            }
+          }
+        });
+  }
 }
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index 0cac61a..d059fb3 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -60,11 +60,13 @@
   for i in range(len(srcs)):
     src = os.path.join(root, srcs[i])
     dest = os.path.join(target_root, 'prebuilts', 'r8', dests[i])
-    print 'Copying: ' + src + ' -> ' + dest
-    copyfile(src, dest)
-    if maps:
-      print 'Copying: ' + src + '.map -> ' + dest + '.map'
-      copyfile(src + '.map', dest + '.map')
+    if os.path.exists(dest):
+      print 'Copying: ' + src + ' -> ' + dest
+      copyfile(src, dest)
+      if maps:
+        print 'Copying: ' + src + '.map -> ' + dest + '.map'
+        copyfile(src + '.map', dest + '.map')
+
 
 def copy_jar_targets(root, target_root, jar_targets, maps):
   srcs = map((lambda t: t[0] + '.jar'), jar_targets)