Merge "Reland "Revised workaround for Android 6.0 dex2oat divergence issue.""
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 627bbe6..f0c9636 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -6,24 +6,34 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfDexItemBasedConstString extends CfInstruction {
 
-  private DexReference item;
+  private final DexReference item;
+  private final ClassNameComputationInfo classNameComputationInfo;
 
-  public CfDexItemBasedConstString(DexReference item) {
+  public CfDexItemBasedConstString(
+      DexReference item, ClassNameComputationInfo classNameComputationInfo) {
     this.item = item;
+    this.classNameComputationInfo = classNameComputationInfo;
   }
 
   public DexReference getItem() {
     return item;
   }
 
+  public ClassNameComputationInfo getClassNameComputationInfo() {
+    return classNameComputationInfo;
+  }
+
   @Override
   public CfDexItemBasedConstString asDexItemBasedConstString() {
     return this;
@@ -52,6 +62,13 @@
   }
 
   @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    if (item.isDexType() && classNameComputationInfo.needsToRegisterTypeReference()) {
+      registry.registerTypeReference(item.asDexType());
+    }
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     builder.addDexItemBasedConstString(state.push(builder.getFactory().stringType).register, item);
   }
diff --git a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
index 1871c41..a55ddfa 100644
--- a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
@@ -6,7 +6,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -15,14 +17,22 @@
   public static final String NAME = "DexItemBasedConstString";
   public static final String SMALI_NAME = "const-string*";
 
-  public DexItemBasedConstString(int register, DexReference string) {
+  private final ClassNameComputationInfo classNameComputationInfo;
+
+  public DexItemBasedConstString(
+      int register, DexReference string, ClassNameComputationInfo classNameComputationInfo) {
     super(register, string);
+    this.classNameComputationInfo = classNameComputationInfo;
   }
 
   public DexReference getItem() {
     return (DexReference) BBBB;
   }
 
+  public ClassNameComputationInfo getClassNameComputationInfo() {
+    return classNameComputationInfo;
+  }
+
   @Override
   public String getName() {
     return NAME;
@@ -68,6 +78,13 @@
   }
 
   @Override
+  public void registerUse(UseRegistry registry) {
+    if (getItem().isDexType() && classNameComputationInfo.needsToRegisterTypeReference()) {
+      registry.registerTypeReference(getItem().asDexType());
+    }
+  }
+
+  @Override
   public void buildIR(IRBuilder builder) {
     builder.addDexItemBasedConstString(AA, (DexReference) BBBB);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0474696..46738e2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1203,14 +1203,14 @@
     optimizationInfo = info;
   }
 
-  public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
+  public void copyMetadata(DexEncodedMethod from) {
     checkIfObsolete();
     // Record that the current method uses identifier name string if the inlinee did so.
-    if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
+    if (from.getOptimizationInfo().useIdentifierNameString()) {
       getMutableOptimizationInfo().markUseIdentifierNameString();
     }
-    if (inlinee.classFileVersion > classFileVersion) {
-      upgradeClassFileVersion(inlinee.getClassFileVersion());
+    if (from.classFileVersion > classFileVersion) {
+      upgradeClassFileVersion(from.getClassFileVersion());
     }
   }
 
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 0450937..0b07644 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -128,6 +128,9 @@
   public final DexString containsMethodName = createString("contains");
   public final DexString startsWithMethodName = createString("startsWith");
   public final DexString endsWithMethodName = createString("endsWith");
+  public final DexString equalsMethodName = createString("equals");
+  public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
+  public final DexString contentEqualsMethodName = createString("contentEquals");
 
   public final DexString valueOfMethodName = createString("valueOf");
   public final DexString toStringMethodName = createString("toString");
@@ -580,6 +583,9 @@
     public final DexMethod contains;
     public final DexMethod startsWith;
     public final DexMethod endsWith;
+    public final DexMethod equals;
+    public final DexMethod equalsIgnoreCase;
+    public final DexMethod contentEqualsCharSequence;
 
     public final DexMethod valueOf;
     public final DexMethod toString;
@@ -590,17 +596,25 @@
       length = createMethod(
           stringDescriptor, lengthMethodName, intDescriptor, DexString.EMPTY_ARRAY);
 
-      DexString[] needsOneCharSequence = new DexString[]{charSequenceDescriptor};
+      DexString[] needsOneCharSequence = { charSequenceDescriptor };
+      DexString[] needsOneString = { stringDescriptor };
+      DexString[] needsOneObject = { objectDescriptor };
+
       contains = createMethod(
           stringDescriptor, containsMethodName, booleanDescriptor, needsOneCharSequence);
-      DexString[] needsOneString = new DexString[]{stringDescriptor};
       startsWith = createMethod(
           stringDescriptor, startsWithMethodName, booleanDescriptor, needsOneString);
       endsWith = createMethod(
           stringDescriptor, endsWithMethodName, booleanDescriptor, needsOneString);
+      equals = createMethod(
+          stringDescriptor, equalsMethodName, booleanDescriptor, needsOneObject);
+      equalsIgnoreCase = createMethod(
+          stringDescriptor, equalsIgnoreCaseMethodName, booleanDescriptor, needsOneString);
+      contentEqualsCharSequence = createMethod(
+          stringDescriptor, contentEqualsMethodName, booleanDescriptor, needsOneCharSequence);
 
       valueOf = createMethod(
-          stringDescriptor, valueOfMethodName, stringDescriptor, new DexString[]{objectDescriptor});
+          stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
       toString = createMethod(
           stringDescriptor, toStringMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
     }
@@ -975,14 +989,13 @@
 
   public ReferenceTypeLatticeElement createReferenceTypeLatticeElement(
       DexType type, boolean isNullable, AppInfo appInfo) {
-    ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
-    if (typeLattice != null) {
-      return isNullable == typeLattice.isNullable() ? typeLattice
-          : typeLattice.getOrCreateDualLattice();
-    }
     synchronized (type) {
-      typeLattice = referenceTypeLatticeElements.get(type);
-      if (typeLattice == null) {
+      ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
+      if (typeLattice != null) {
+        typeLattice = isNullable == typeLattice.isNullable() ? typeLattice
+            : typeLattice.getOrCreateDualLattice();
+        assert typeLattice.isNullable() == isNullable;
+      } else {
         if (type.isClassType()) {
           if (!type.isUnknown() && type.isInterface()) {
             typeLattice = new ClassTypeLatticeElement(
@@ -1000,8 +1013,9 @@
         }
         referenceTypeLatticeElements.put(type, typeLattice);
       }
+      assert typeLattice.isNullable() == isNullable;
+      return typeLattice;
     }
-    return typeLattice;
   }
 
   private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items,
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 6eb69b8..c0413e0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.utils.EncodedValueUtils;
 import java.util.Arrays;
 import org.objectweb.asm.Handle;
@@ -719,9 +720,20 @@
   }
 
   public static class DexItemBasedValueString extends NestedDexValue<DexReference> {
+    private final ClassNameComputationInfo classNameComputationInfo;
 
     public DexItemBasedValueString(DexReference value) {
+      this(value, ClassNameComputationInfo.none());
+    }
+
+    public DexItemBasedValueString(
+        DexReference value, ClassNameComputationInfo classNameComputationInfo) {
       super(value);
+      this.classNameComputationInfo = classNameComputationInfo;
+    }
+
+    public ClassNameComputationInfo getClassNameComputationInfo() {
+      return classNameComputationInfo;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 3ec7971..2b54a4b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -13,25 +13,38 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 
 public class DexItemBasedConstString extends ConstInstruction {
 
   private final DexReference item;
+  private final ClassNameComputationInfo classNameComputationInfo;
 
   public DexItemBasedConstString(Value dest, DexReference item) {
+    this(dest, item, ClassNameComputationInfo.none());
+  }
+
+  public DexItemBasedConstString(
+      Value dest, DexReference item, ClassNameComputationInfo classNameComputationInfo) {
     super(dest);
     dest.markNeverNull();
     this.item = item;
+    this.classNameComputationInfo = classNameComputationInfo;
   }
 
   public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
-    return new DexItemBasedConstString(newValue, original.getItem());
+    return new DexItemBasedConstString(
+        newValue, original.getItem(), original.classNameComputationInfo);
   }
 
   public DexReference getItem() {
     return item;
   }
 
+  public ClassNameComputationInfo getClassNameComputationInfo() {
+    return classNameComputationInfo;
+  }
+
   @Override
   public boolean isDexItemBasedConstString() {
     return true;
@@ -45,7 +58,10 @@
   @Override
   public void buildDex(DexBuilder builder) {
     int dest = builder.allocatedRegister(outValue(), getNumber());
-    builder.add(this, new com.android.tools.r8.code.DexItemBasedConstString(dest, item));
+    builder.add(
+        this,
+        new com.android.tools.r8.code.DexItemBasedConstString(
+            dest, item, classNameComputationInfo));
   }
 
   @Override
@@ -99,7 +115,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfDexItemBasedConstString(item));
+    builder.add(new CfDexItemBasedConstString(item, classNameComputationInfo));
   }
 
   @Override
@@ -109,6 +125,6 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
+    return TypeLatticeElement.stringClassType(appInfo);
   }
 }
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 644dfa3..b4ad228 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
@@ -726,6 +726,12 @@
     return isConstant() && getConstInstruction().isDexItemBasedConstString();
   }
 
+  public boolean isDexItemBasedConstStringThatNeedsToComputeClassName() {
+    return isDexItemBasedConstString()
+        && getConstInstruction().asDexItemBasedConstString()
+            .getClassNameComputationInfo().needsToComputeClassName();
+  }
+
   public boolean isConstClass() {
     return isConstant() && getConstInstruction().isConstClass();
   }
@@ -762,7 +768,9 @@
    * Returns whether this value is known to never be <code>null</code>.
    */
   public boolean isNeverNull() {
-    return neverNull || (definition != null && definition.isNonNull());
+    return neverNull
+        || (definition != null && definition.isNonNull())
+        || (typeLattice.isReference() && typeLattice.nullElement().isDefinitelyNotNull());
   }
 
   public boolean canBeNull() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index a658474..8f32081 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -45,7 +44,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StackValue;
 import com.android.tools.r8.ir.code.StackValues;
-import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -385,75 +383,59 @@
 
   private void rewriteIincPatterns() {
     for (BasicBlock block : code.blocks) {
-      if (block.getInstructions().size() < IINC_PATTERN_SIZE) {
-        continue;
-      }
-      int earliestAddInstrIdx = 2;
-      int latestAddInstrIdx = block.getInstructions().size() - 3;
       ListIterator<Instruction> it = block.getInstructions().listIterator();
-      int instrIdx = -1;
-      while (it.hasNext()) {
-        ++instrIdx;
-        Instruction current = it.next();
-        if (instrIdx < earliestAddInstrIdx || latestAddInstrIdx < instrIdx || !current.isAdd()) {
-          continue;
-        }
-        it.previous();
-        it.previous();
-        it.previous();
-        Instruction instruction0 = it.next();
-        Instruction instruction1 = it.next();
-        Add instruction2 = it.next().asAdd();
-        Store instruction3 = it.next().asStore();
-
-        // Set bail-out position after Add.
-        it.previous();
-
-        if (instruction3 == null) {
+      // Test that we have enough instructions for iinc.
+      while (IINC_PATTERN_SIZE <= block.getInstructions().size() - it.nextIndex()) {
+        Instruction loadOrConst1 = it.next();
+        if (!loadOrConst1.isLoad() && !loadOrConst1.isConstNumber()) {
           continue;
         }
         Load load;
         ConstNumber constNumber;
-
-        if (instruction0.isLoad()) {
-          load = instruction0.asLoad();
-          constNumber = instruction1.asConstNumber();
+        if (loadOrConst1.isLoad()) {
+          load = loadOrConst1.asLoad();
+          constNumber = it.next().asConstNumber();
         } else {
-          constNumber = instruction0.asConstNumber();
-          load = instruction1.asLoad();
+          load = it.next().asLoad();
+          constNumber = loadOrConst1.asConstNumber();
         }
-
+        Instruction add = it.next().asAdd();
+        Instruction store = it.next().asStore();
+        // Reset pointer to load.
+        it.previous();
+        it.previous();
+        it.previous();
+        it.previous();
         if (load == null
             || constNumber == null
+            || add == null
+            || store == null
             || constNumber.outValue().getTypeLattice() != TypeLatticeElement.INT) {
+          it.next();
           continue;
         }
-
         int increment = constNumber.getIntValue();
         if (increment < Byte.MIN_VALUE || Byte.MAX_VALUE < increment) {
+          it.next();
           continue;
         }
-
-        int register = getLocalRegister(load.inValues().get(0));
-        if (register != getLocalRegister(instruction3.outValue())) {
+        if (getLocalRegister(load.src()) != getLocalRegister(store.outValue())) {
+          it.next();
           continue;
         }
-        Position position = instruction2.getPosition();
-        if (position != instruction0.getPosition()
-            || position != instruction1.getPosition()
-            || position != instruction3.getPosition()) {
+        Position position = add.getPosition();
+        if (position != load.getPosition()
+            || position != constNumber.getPosition()
+            || position != store.getPosition()) {
           continue;
         }
-        it.previous();
-        it.previous();
-        it.previous();
         it.remove();
         it.next();
         it.remove();
         it.next();
         it.remove();
         it.next();
-        Inc inc = new Inc(instruction3.outValue(), load.inValues().get(0), increment);
+        Inc inc = new Inc(store.outValue(), load.inValues().get(0), increment);
         inc.setPosition(position);
         inc.setBlock(block);
         it.set(inc);
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 cf3d788..38842da 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
@@ -76,6 +76,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -178,7 +179,8 @@
       assert appInfo.hasLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
       AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      this.nonNullTracker = new NonNullTracker(appInfo);
+      this.nonNullTracker =
+          new NonNullTracker(appInfo, libraryMethodsReturningNonNull(appInfo.dexItemFactory));
       this.inliner = new Inliner(appViewWithLiveness, this, options);
       this.outliner = new Outliner(appInfoWithLiveness, options, this);
       this.memberValuePropagation =
@@ -291,6 +293,16 @@
     return methods;
   }
 
+  // Library methods listed here are based on their original implementations. That is, we assume
+  // these cannot be overridden.
+  public static Set<DexMethod> libraryMethodsReturningNonNull(DexItemFactory factory) {
+    return ImmutableSet.of(
+        factory.stringMethods.valueOf,
+        factory.classMethods.getName,
+        factory.classMethods.getSimpleName
+    );
+  }
+
   private void removeLambdaDeserializationMethods() {
     if (lambdaRewriter != null) {
       lambdaRewriter.removeLambdaDeserializationMethods(appInfo.classes());
@@ -890,12 +902,6 @@
       inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
     }
 
-    // Either marked by IdentifierNameStringMarker or propagated from inlinee.
-    // Then, make it visible to IdentifierMinifier.
-    if (method.getOptimizationInfo().useIdentifierNameString()) {
-      feedback.markUseIdentifierNameString(method);
-    }
-
     if (appInfo.hasLiveness()) {
       // Reflection optimization 1. getClass() -> const-class
       codeRewriter.rewriteGetClass(code);
@@ -911,6 +917,12 @@
       assert code.isConsistentSSA();
     }
 
+    // Either marked by IdentifierNameStringMarker or name reflection, or propagated from inlinee.
+    // Then, make it visible to IdentifierMinifier.
+    if (method.getOptimizationInfo().useIdentifierNameString()) {
+      feedback.markUseIdentifierNameString(method);
+    }
+
     if (devirtualizer != null) {
       assert code.verifyTypes(appInfo, appView, graphLense());
       devirtualizer.devirtualizeInvokeInterface(code, method.method.getHolder());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
index 3ef25f5..5dbdbe1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
@@ -59,7 +59,7 @@
   }
 
   @Override
-  public void markUseIdentifierNameString(DexEncodedMethod method) {
+  public synchronized void markUseIdentifierNameString(DexEncodedMethod method) {
     getOptimizationInfoForUpdating(method).markUseIdentifierNameString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 01555da..7a9ec00 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -531,6 +531,7 @@
                   encodedMethod.annotations,
                   encodedMethod.parameterAnnotationsList,
                   encodedMethod.getCode());
+          newMethod.copyMetadata(encodedMethod);
           rewriter.methodMapping.put(encodedMethod.method, callTarget);
           // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
           DexCode dexCode = newMethod.getCode().asDexCode();
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 553eaa2..98ee72c 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
@@ -151,8 +151,10 @@
   private final Set<DexMethod> libraryMethodsReturningReceiver;
   private final InternalOptions options;
 
-  public CodeRewriter(IRConverter converter,
-      Set<DexMethod> libraryMethodsReturningReceiver, InternalOptions options) {
+  public CodeRewriter(
+      IRConverter converter,
+      Set<DexMethod> libraryMethodsReturningReceiver,
+      InternalOptions options) {
     this.converter = converter;
     this.appInfo = converter.appInfo;
     this.options = options;
@@ -1531,15 +1533,15 @@
       return;
     }
     AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
+    Set<Value> needToWidenValues = Sets.newIdentityHashSet();
+    Set<Value> needToNarrowValues = Sets.newIdentityHashSet();
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         if (invoke.outValue() != null && !invoke.outValue().hasLocalInfo()) {
-          boolean isLibraryMethodReturningReceiver =
-              libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod());
-          if (isLibraryMethodReturningReceiver) {
+          if (libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod())) {
             if (checkArgumentType(invoke, invoke.getInvokedMethod(), 0)) {
               invoke.outValue().replaceUsers(invoke.arguments().get(0));
               invoke.setOutValue(null);
@@ -1554,11 +1556,18 @@
               if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
                 int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
                 // Replace the out value of the invoke with the argument and ignore the out value.
-                if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
-                    argumentIndex)) {
+                if (argumentIndex != -1
+                    && checkArgumentType(invoke, target.method, argumentIndex)) {
                   Value argument = invoke.arguments().get(argumentIndex);
-                  assert invoke.outValue().verifyCompatible(argument.outType());
-                  invoke.outValue().replaceUsers(argument);
+                  Value outValue = invoke.outValue();
+                  assert outValue.verifyCompatible(argument.outType());
+                  if (argument.getTypeLattice().lessThanOrEqual(
+                      outValue.getTypeLattice(), appInfo)) {
+                    needToNarrowValues.addAll(outValue.affectedValues());
+                  } else {
+                    needToWidenValues.addAll(outValue.affectedValues());
+                  }
+                  outValue.replaceUsers(argument);
                   invoke.setOutValue(null);
                 }
               }
@@ -1567,6 +1576,17 @@
         }
       }
     }
+    if (!needToWidenValues.isEmpty() || !needToNarrowValues.isEmpty()) {
+      TypeAnalysis analysis = new TypeAnalysis(appInfo, code.method);
+      // If out value of invoke < argument (e.g., losing non-null info), widen users type.
+      if (!needToWidenValues.isEmpty()) {
+        analysis.widening(needToWidenValues);
+      }
+      // Otherwise, i.e., argument has more precise types, narrow users type.
+      if (!needToNarrowValues.isEmpty()) {
+        analysis.narrowing(needToNarrowValues);
+      }
+    }
     assert code.isConsistentGraph();
   }
 
@@ -1726,7 +1746,7 @@
   }
 
   // Check if the static put is a constant derived from the class holding the method.
-  // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
+  // This checks for java.lang.Class.get*Name.
   private boolean isClassNameConstantOf(DexClass clazz, StaticPut put) {
     if (put.getField().type != dexItemFactory.stringType) {
       return false;
@@ -1740,8 +1760,7 @@
   private boolean isClassNameConstantOf(DexClass clazz, Instruction instruction) {
     if (instruction.isInvokeVirtual()) {
       InvokeVirtual invoke = instruction.asInvokeVirtual();
-      if ((invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName
-          || invoke.getInvokedMethod() == dexItemFactory.classMethods.getName)
+      if (dexItemFactory.classMethods.isReflectiveNameLookup(invoke.getInvokedMethod())
           && !invoke.inValues().get(0).isPhi()
           && invoke.inValues().get(0).definition.isConstClass()
           && invoke.inValues().get(0).definition.asConstClass().getValue() == clazz.type) {
@@ -1785,7 +1804,9 @@
             } else if (inValue.isDexItemBasedConstString()) {
               DexItemBasedConstString cnst =
                   inValue.getConstInstruction().asDexItemBasedConstString();
-              encodedField.setStaticValue(new DexItemBasedValueString(cnst.getItem()));
+              assert !cnst.getClassNameComputationInfo().needsToComputeClassName();
+              encodedField.setStaticValue(
+                  new DexItemBasedValueString(cnst.getItem(), cnst.getClassNameComputationInfo()));
             } else {
               assert false;
             }
@@ -1886,6 +1907,9 @@
               }
               DexField field = put.getField();
               if (clazz.definesStaticField(field)) {
+                if (put.inValue().isDexItemBasedConstStringThatNeedsToComputeClassName()) {
+                  continue;
+                }
                 if (put.inValue().isConstant()) {
                   if ((field.type.isClassType() || field.type.isArrayType())
                       && put.inValue().isZero()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index bad5456..be94302 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -427,7 +428,8 @@
       assert IteratorUtils.peekNext(blockIterator) == block;
 
       // Kick off the tracker to add non-null IRs only to the inlinee blocks.
-      new NonNullTracker(appView.appInfo())
+      new NonNullTracker(appView.appInfo(),
+          IRConverter.libraryMethodsReturningNonNull(appView.dexItemFactory()))
           .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
       assert !blockIterator.hasNext();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 63d0242..3c27a60 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -647,7 +647,7 @@
                 context.accessFlags.unsetBridge();
               }
 
-              context.copyMetadataFromInlinee(target);
+              context.copyMetadata(target);
               code.copyMetadataFromInlinee(inlinee.code);
             }
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 29c7979..b82c743 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -178,7 +178,12 @@
           DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
           if (target != null) {
             if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
-              invoke.outValue().markNeverNull();
+              Value knownToBeNonNullValue = invoke.outValue();
+              knownToBeNonNullValue.markNeverNull();
+              TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+              assert typeLattice.isNullable() && typeLattice.isReference();
+              knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+              affectedValues.addAll(knownToBeNonNullValue.affectedValues());
             }
             if (target.getOptimizationInfo().returnsConstant()) {
               long constant = target.getOptimizationInfo().getReturnedConstant();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 77339e0..1e62c36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -34,9 +35,11 @@
 public class NonNullTracker {
 
   private final AppInfo appInfo;
+  private final Set<DexMethod> libraryMethodsReturningNonNull;
 
-  public NonNullTracker(AppInfo appInfo) {
+  public NonNullTracker(AppInfo appInfo, Set<DexMethod> libraryMethodsReturningNonNull) {
     this.appInfo = appInfo;
+    this.libraryMethodsReturningNonNull = libraryMethodsReturningNonNull;
   }
 
   @VisibleForTesting
@@ -76,102 +79,48 @@
   public void addNonNullInPart(
       IRCode code, ListIterator<BasicBlock> blockIterator, Predicate<BasicBlock> blockTester) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
+    Set<Value> knownToBeNonNullValues = Sets.newIdentityHashSet();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (!blockTester.test(block)) {
         continue;
       }
-      // Add non-null after instructions that implicitly indicate receiver/array is not null.
+      // Add non-null after
+      // 1) invocations that call non-overridable library methods that are known to return non null.
+      // 2) instructions that implicitly indicate receiver/array is not null.
+      // TODO(b/71500340): We can add non-null IRs for known-to-be-non-null parameters here.
       InstructionListIterator iterator = block.listIterator();
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
-        if (!throwsOnNullInput(current)) {
-          continue;
-        }
-        Value knownToBeNonNullValue = getNonNullInput(current);
-        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-        // Avoid adding redundant non-null instruction.
-        if (knownToBeNonNullValue.isNeverNull() || !isNonNullCandidate(typeLattice)) {
+        if (current.isInvokeMethod()
+            && libraryMethodsReturningNonNull.contains(
+                current.asInvokeMethod().getInvokedMethod())) {
+          Value knownToBeNonNullValue = current.outValue();
+          // Avoid adding redundant non-null instruction.
           // Otherwise, we will have something like:
           // non_null_rcv <- non-null(rcv)
           // ...
           // another_rcv <- non-null(non_null_rcv)
-          continue;
-        }
-        // First, if the current block has catch handler, split into two blocks, e.g.,
-        //
-        // ...x
-        // invoke(rcv, ...)
-        // ...y
-        //
-        //   ~>
-        //
-        // ...x
-        // invoke(rcv, ...)
-        // goto A
-        //
-        // A: ...y // blockWithNonNullInstruction
-        boolean split = block.hasCatchHandlers();
-        BasicBlock blockWithNonNullInstruction =
-            split ? iterator.split(code, blockIterator) : block;
-
-        // Find all users of the original value that are dominated by either the current block
-        // or the new split-off block. Since NPE can be explicitly caught, nullness should be
-        // propagated through dominance.
-        Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
-        Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
-        Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
-        DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-        Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
-        for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
-          dominatedBlocks.add(dominatee);
-          InstructionListIterator dominateeIterator = dominatee.listIterator();
-          if (dominatee == blockWithNonNullInstruction && !split) {
-            // In the block where the non null instruction will be inserted, skip instructions up
-            // to and including the insertion point.
-            dominateeIterator.nextUntil(instruction -> instruction == current);
-          }
-          while (dominateeIterator.hasNext()) {
-            Instruction potentialUser = dominateeIterator.next();
-            if (users.contains(potentialUser)) {
-              dominatedUsers.add(potentialUser);
-            }
+          if (knownToBeNonNullValue != null && isNonNullCandidate(knownToBeNonNullValue)) {
+            knownToBeNonNullValues.add(knownToBeNonNullValue);
           }
         }
-        for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
-          IntList dominatedPredecessorIndexes =
-              findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
-          if (!dominatedPredecessorIndexes.isEmpty()) {
-            dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
+        if (throwsOnNullInput(current)) {
+          Value knownToBeNonNullValue = getNonNullInput(current);
+          if (isNonNullCandidate(knownToBeNonNullValue)) {
+            knownToBeNonNullValues.add(knownToBeNonNullValue);
           }
         }
-
-        // Only insert non-null instruction if it is ever used.
-        if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
-          // Add non-null fake IR, e.g.,
-          // ...x
-          // invoke(rcv, ...)
-          // goto A
-          // ...
-          // A: non_null_rcv <- non-null(rcv)
-          // ...y
-          Value nonNullValue = code.createValue(
-              typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
-          affectedValues.addAll(knownToBeNonNullValue.affectedValues());
-          NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
-          nonNull.setPosition(current.getPosition());
-          if (blockWithNonNullInstruction != block) {
-            // If we split, add non-null IR on top of the new split block.
-            blockWithNonNullInstruction.listIterator().add(nonNull);
-          } else {
-            // Otherwise, just add it to the current block at the position of the iterator.
-            iterator.add(nonNull);
-          }
-
-          // Replace all users of the original value that are dominated by either the current
-          // block or the new split-off block.
-          knownToBeNonNullValue.replaceSelectiveUsers(
-              nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+        if (!knownToBeNonNullValues.isEmpty()) {
+          addNonNullForValues(
+              code,
+              blockIterator,
+              block,
+              iterator,
+              current,
+              knownToBeNonNullValues,
+              affectedValues);
+          knownToBeNonNullValues.clear();
         }
       }
 
@@ -200,9 +149,8 @@
         // ...
         If theIf = block.exit().asIf();
         Value knownToBeNonNullValue = theIf.inValues().get(0);
-        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-        // Avoid adding redundant non-null instruction (or non-null of non-object types).
-        if (!knownToBeNonNullValue.isNeverNull() && isNonNullCandidate(typeLattice)) {
+        // Avoid adding redundant non-null instruction.
+        if (isNonNullCandidate(knownToBeNonNullValue)) {
           BasicBlock target = theIf.targetFromNonNullObject();
           // Ignore uncommon empty blocks.
           if (!target.isEmpty()) {
@@ -228,6 +176,7 @@
               }
               // Avoid adding a non-null for the value without meaningful users.
               if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+                TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
                 Value nonNullValue = code.createValue(
                     typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
                 affectedValues.addAll(knownToBeNonNullValue.affectedValues());
@@ -249,6 +198,93 @@
     }
   }
 
+  private void addNonNullForValues(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      BasicBlock block,
+      InstructionListIterator iterator,
+      Instruction current,
+      Set<Value> knownToBeNonNullValues,
+      Set<Value> affectedValues) {
+    // First, if the current block has catch handler, split into two blocks, e.g.,
+    //
+    // ...x
+    // invoke(rcv, ...)
+    // ...y
+    //
+    //   ~>
+    //
+    // ...x
+    // invoke(rcv, ...)
+    // goto A
+    //
+    // A: ...y // blockWithNonNullInstruction
+    boolean split = block.hasCatchHandlers();
+    BasicBlock blockWithNonNullInstruction = split ? iterator.split(code, blockIterator) : block;
+    DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+
+    for (Value knownToBeNonNullValue : knownToBeNonNullValues) {
+      // Find all users of the original value that are dominated by either the current block
+      // or the new split-off block. Since NPE can be explicitly caught, nullness should be
+      // propagated through dominance.
+      Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
+      Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+      Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
+      Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
+      for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
+        dominatedBlocks.add(dominatee);
+        InstructionListIterator dominateeIterator = dominatee.listIterator();
+        if (dominatee == blockWithNonNullInstruction && !split) {
+          // In the block where the non null instruction will be inserted, skip instructions up to
+          // and including the insertion point.
+          dominateeIterator.nextUntil(instruction -> instruction == current);
+        }
+        while (dominateeIterator.hasNext()) {
+          Instruction potentialUser = dominateeIterator.next();
+          if (users.contains(potentialUser)) {
+            dominatedUsers.add(potentialUser);
+          }
+        }
+      }
+      for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
+        IntList dominatedPredecessorIndexes =
+            findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
+        if (!dominatedPredecessorIndexes.isEmpty()) {
+          dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
+        }
+      }
+
+      // Only insert non-null instruction if it is ever used.
+      if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+        // Add non-null fake IR, e.g.,
+        // ...x
+        // invoke(rcv, ...)
+        // goto A
+        // ...
+        // A: non_null_rcv <- non-null(rcv)
+        // ...y
+        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+        Value nonNullValue =
+            code.createValue(typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
+        affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+        NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
+        nonNull.setPosition(current.getPosition());
+        if (blockWithNonNullInstruction != block) {
+          // If we split, add non-null IR on top of the new split block.
+          blockWithNonNullInstruction.listIterator().add(nonNull);
+        } else {
+          // Otherwise, just add it to the current block at the position of the iterator.
+          iterator.add(nonNull);
+        }
+
+        // Replace all users of the original value that are dominated by either the current block
+        // or the new split-off block.
+        knownToBeNonNullValue.replaceSelectiveUsers(
+            nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+      }
+    }
+  }
+
   private IntList findDominatedPredecessorIndexesInPhi(
       Phi user, Value knownToBeNonNullValue, Set<BasicBlock> dominatedBlocks) {
     assert user.getOperands().contains(knownToBeNonNullValue);
@@ -274,10 +310,13 @@
     return predecessorIndexes;
   }
 
-  private boolean isNonNullCandidate(TypeLatticeElement typeLattice) {
+  private boolean isNonNullCandidate(Value knownToBeNonNullValue) {
+    TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
     return
+        // redundant
+        !knownToBeNonNullValue.isNeverNull()
         // v <- non-null NULL ?!
-        !typeLattice.isNull()
+        && !typeLattice.isConstantNull()
         // v <- non-null known-to-be-non-null // redundant
         && typeLattice.isNullable()
         // e.g., v <- non-null INT ?!
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
new file mode 100644
index 0000000..ecd3a7a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2018, 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;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getCanonicalNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption;
+import com.google.common.base.Strings;
+
+public class ReflectionOptimizer {
+
+  public static class ClassNameComputationInfo {
+    public enum ClassNameComputationOption {
+      NONE,
+      NAME,           // getName()
+      TYPE_NAME,      // getTypeName()
+      CANONICAL_NAME, // getCanonicalName()
+      SIMPLE_NAME;    // getSimpleName()
+
+      boolean needsToComputeClassName() {
+        return this != NONE;
+      }
+
+      boolean needsToRegisterTypeReference() {
+        return this == SIMPLE_NAME;
+      }
+    }
+
+    private static final ClassNameComputationInfo DEFAULT_INSTANCE =
+        new ClassNameComputationInfo(ClassNameComputationOption.NONE, 0);
+
+    final ClassNameComputationOption classNameComputationOption;
+    final int arrayDepth;
+
+    public ClassNameComputationInfo(
+        ClassNameComputationOption classNameComputationOption, int arrayDepth) {
+      this.classNameComputationOption = classNameComputationOption;
+      this.arrayDepth = arrayDepth;
+    }
+
+    public static ClassNameComputationInfo none() {
+      return DEFAULT_INSTANCE;
+    }
+
+    public boolean needsToComputeClassName() {
+      return classNameComputationOption.needsToComputeClassName();
+    }
+
+    public boolean needsToRegisterTypeReference() {
+      return classNameComputationOption.needsToRegisterTypeReference();
+    }
+  }
+
+  public static String computeClassName(
+      DexString descriptor, DexClass holder, ClassNameComputationInfo classNameComputationInfo) {
+    return computeClassName(
+        descriptor.toString(),
+        holder,
+        classNameComputationInfo.classNameComputationOption,
+        classNameComputationInfo.arrayDepth);
+  }
+
+  public static String computeClassName(
+      String descriptor,
+      DexClass holder,
+      ClassNameComputationOption classNameComputationOption,
+      int arrayDepth) {
+    String name;
+    switch (classNameComputationOption) {
+      case NAME:
+        name = getClassNameFromDescriptor(descriptor);
+        if (arrayDepth > 0) {
+          name = Strings.repeat("[", arrayDepth) + "L" + name + ";";
+        }
+        break;
+      case TYPE_NAME:
+        // TODO(b/119426668): desugar Type#getTypeName
+        throw new Unreachable("Type#getTypeName not supported yet");
+        // name = getClassNameFromDescriptor(descriptor);
+        // if (arrayDepth > 0) {
+        //   name = name + Strings.repeat("[]", arrayDepth);
+        // }
+        // break;
+      case CANONICAL_NAME:
+        name = getCanonicalNameFromDescriptor(descriptor);
+        if (arrayDepth > 0) {
+          name = name + Strings.repeat("[]", arrayDepth);
+        }
+        break;
+      case SIMPLE_NAME:
+        assert holder != null;
+        boolean renamed = !descriptor.equals(holder.type.toDescriptorString());
+        boolean needsToRetrieveInnerName = holder.isMemberClass() || holder.isLocalClass();
+        if (!renamed && needsToRetrieveInnerName) {
+          name = holder.getInnerClassAttributeForThisClass().getInnerName().toString();
+        } else {
+          name = getSimpleClassNameFromDescriptor(descriptor);
+        }
+        if (arrayDepth > 0) {
+          name = name + Strings.repeat("[]", arrayDepth);
+        }
+        break;
+      default:
+        throw new Unreachable(
+            "Unexpected ClassNameComputationOption: '" + classNameComputationOption + "'");
+    }
+    return name;
+  }
+
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index a55015d..3ba9289 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -249,8 +249,10 @@
     instructionIterator.previous();
 
     // Unlink all blocks that are dominated by successor.
-    DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-    blocksToBeRemoved.addAll(block.unlink(normalSuccessorBlock, dominatorTree));
+    {
+      DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+      blocksToBeRemoved.addAll(block.unlink(normalSuccessorBlock, dominatorTree));
+    }
 
     // Insert constant null before the instruction.
     instructionIterator.previous();
@@ -284,6 +286,9 @@
               return;
             }
             if (!appView.dexItemFactory().npeType.isSubtypeOf(guard, appView.appInfo())) {
+              // TODO(christofferqa): Consider updating previous dominator tree instead of
+              // rebuilding it from scratch.
+              DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
               blocksToBeRemoved.addAll(block.unlink(target, dominatorTree));
             }
           });
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 9fec63a..782951d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -4,9 +4,10 @@
 package com.android.tools.r8.ir.optimize.string;
 
 import static com.android.tools.r8.ir.optimize.CodeRewriter.removeOrReplaceByDebugLocalWrite;
-import static com.android.tools.r8.utils.DescriptorUtils.getCanonicalNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.CANONICAL_NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.SIMPLE_NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.computeClassName;
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -18,20 +19,21 @@
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.base.Strings;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public class StringOptimizer {
 
-  final ThrowingInfo throwingInfo;
+  private final ThrowingInfo throwingInfo;
 
   public StringOptimizer(InternalOptions options) {
     throwingInfo = options.isGeneratingClassFiles()
@@ -43,6 +45,9 @@
   // boolean String#startsWith(str)
   // boolean String#endsWith(str)
   // boolean String#contains(str)
+  // boolean String#equals(str)
+  // boolean String#equalsIgnoreCase(str)
+  // boolean String#contentEquals(str)
   public void computeTrivialOperationsOnConstString(IRCode code, DexItemFactory factory) {
     if (!code.hasConstString) {
       return;
@@ -67,6 +72,12 @@
         operatorWithString = (rcv, arg) -> rcv.startsWith(arg) ? 1 : 0;
       } else if (invokedMethod == factory.stringMethods.endsWith) {
         operatorWithString = (rcv, arg) -> rcv.endsWith(arg) ? 1 : 0;
+      } else if (invokedMethod == factory.stringMethods.equals) {
+        operatorWithString = (rcv, arg) -> rcv.equals(arg) ? 1 : 0;
+      } else if (invokedMethod == factory.stringMethods.equalsIgnoreCase) {
+        operatorWithString = (rcv, arg) -> rcv.equalsIgnoreCase(arg) ? 1 : 0;
+      } else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
+        operatorWithString = (rcv, arg) -> rcv.contentEquals(arg) ? 1 : 0;
       }
       if (operatorWithNoArg == null && operatorWithString == null) {
         continue;
@@ -104,6 +115,7 @@
 
   // Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
   public void rewriteClassGetName(IRCode code, AppInfo appInfo) {
+    boolean markUseIdentifierNameString = false;
     InstructionIterator it = code.instructionIterator();
     while (it.hasNext()) {
       Instruction instr = it.next();
@@ -147,29 +159,18 @@
         continue;
       }
 
+      DexItemBasedConstString deferred = null;
       String name = null;
       if (invokedMethod == appInfo.dexItemFactory.classMethods.getName) {
         if (code.options.enableMinification) {
-          // TODO(b/118536394): Add support minification and pinning.
-          //   May need store array depth on DexItemBasedConstString.
-          //   May need enum on DexItemBasedConstString to distinguish name computation.
-          continue;
-        }
-        name = getClassNameFromDescriptor(baseType.toDescriptorString());
-        if (arrayDepth > 0) {
-          name = Strings.repeat("[", arrayDepth) + "L" + name + ";";
+          deferred = new DexItemBasedConstString(
+              invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
+        } else {
+          name = computeClassName(baseType.toDescriptorString(), holder, NAME, arrayDepth);
         }
       } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getTypeName) {
         // TODO(b/119426668): desugar Type#getTypeName
         continue;
-        // if (code.options.enableMinification) {
-        //   // TODO(b/118536394): Add support minification and pinning.
-        //   continue;
-        // }
-        // name = getClassNameFromDescriptor(baseType.toDescriptorString());
-        // if (arrayDepth > 0) {
-        //   name = name + Strings.repeat("[]", arrayDepth);
-        // }
       } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getCanonicalName) {
         // Always returns null if the target type is local or anonymous class.
         if (holder.isLocalClass() || holder.isAnonymousClass()) {
@@ -177,12 +178,14 @@
           it.replaceCurrentInstruction(constNull);
         } else {
           if (code.options.enableMinification) {
-            // TODO(b/118536394): Add support minification and pinning.
-            continue;
-          }
-          name = getCanonicalNameFromDescriptor(baseType.toDescriptorString());
-          if (arrayDepth > 0) {
-            name = name + Strings.repeat("[]", arrayDepth);
+            deferred =
+                new DexItemBasedConstString(
+                    invoke.outValue(),
+                    baseType,
+                    new ClassNameComputationInfo(CANONICAL_NAME, arrayDepth));
+          } else {
+            name = computeClassName(
+                baseType.toDescriptorString(), holder, CANONICAL_NAME, arrayDepth);
           }
         }
       } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getSimpleName) {
@@ -191,16 +194,10 @@
           name = "";
         } else {
           if (code.options.enableMinification) {
-            // TODO(b/118536394): Add support minification and pinning.
-            continue;
-          }
-          if (holder.isMemberClass() || holder.isLocalClass()) {
-            name = holder.getInnerClassAttributeForThisClass().getInnerName().toString();
+            deferred = new DexItemBasedConstString(
+                invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
           } else {
-            name = getSimpleClassNameFromDescriptor(baseType.toDescriptorString());
-          }
-          if (arrayDepth > 0) {
-            name = name + Strings.repeat("[]", arrayDepth);
+            name = computeClassName(baseType.toDescriptorString(), holder, SIMPLE_NAME, arrayDepth);
           }
         }
       }
@@ -210,8 +207,14 @@
         ConstString constString =
             new ConstString(stringValue, appInfo.dexItemFactory.createString(name), throwingInfo);
         it.replaceCurrentInstruction(constString);
+      } else if (deferred != null) {
+        it.replaceCurrentInstruction(deferred);
+        markUseIdentifierNameString = true;
       }
     }
+    if (markUseIdentifierNameString) {
+      code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
+    }
   }
 
   // String#valueOf(null) -> "null"
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 6928d3e..61695f1 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.computeClassName;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 
 import com.android.tools.r8.cf.code.CfConstString;
@@ -23,7 +24,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardClassFilter;
-import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.List;
 import java.util.Map;
 
@@ -36,23 +36,19 @@
   private final AppInfoWithLiveness appInfo;
   private final ProguardClassFilter adaptClassStrings;
   private final NamingLens lens;
-  private final Object2BooleanMap<DexReference> identifierNameStrings;
 
   IdentifierMinifier(
       AppInfoWithLiveness appInfo, ProguardClassFilter adaptClassStrings, NamingLens lens) {
     this.appInfo = appInfo;
     this.adaptClassStrings = adaptClassStrings;
     this.lens = lens;
-    this.identifierNameStrings = appInfo.identifierNameStrings;
   }
 
   void run() {
     if (!adaptClassStrings.isEmpty()) {
       adaptClassStrings();
     }
-    if (!identifierNameStrings.isEmpty()) {
-      replaceIdentifierNameString();
-    }
+    replaceDexItemBasedConstString();
   }
 
   private void adaptClassStrings() {
@@ -120,28 +116,35 @@
     return originalLiteral;
   }
 
-  // TODO(christofferqa): Rename to replaceDexItemBasedConstString.
-  private void replaceIdentifierNameString() {
+  private void replaceDexItemBasedConstString() {
     for (DexProgramClass clazz : appInfo.classes()) {
       // Some const strings could be moved to field's static value (from <clinit>).
       for (DexEncodedField field : clazz.staticFields()) {
-        replaceIdentifierNameStringInStaticField(field);
+        replaceDexItemBasedConstStringInStaticField(field);
       }
-      clazz.forEachMethod(this::replaceIdentifierNameStringInMethod);
+      clazz.forEachMethod(this::replaceDexItemBasedConstStringInMethod);
     }
   }
 
-  private void replaceIdentifierNameStringInStaticField(DexEncodedField encodedField) {
+  private void replaceDexItemBasedConstStringInStaticField(DexEncodedField encodedField) {
     assert encodedField.accessFlags.isStatic();
     DexValue staticValue = encodedField.getStaticValue();
     if (staticValue instanceof DexItemBasedValueString) {
-      DexReference original = ((DexItemBasedValueString) staticValue).getValue();
-      encodedField.setStaticValue(
-          new DexValueString(lens.lookupName(original, appInfo.dexItemFactory)));
+      DexItemBasedValueString dexItemBasedValueString = (DexItemBasedValueString) staticValue;
+      DexReference original = dexItemBasedValueString.getValue();
+      DexString replacement =
+          dexItemBasedValueString.getClassNameComputationInfo().needsToComputeClassName()
+              ? appInfo.dexItemFactory.createString(
+                  computeClassName(
+                      lens.lookupDescriptor(original.asDexType()),
+                      appInfo.definitionFor(original.asDexType()),
+                      dexItemBasedValueString.getClassNameComputationInfo()))
+              : lens.lookupName(original, appInfo.dexItemFactory);
+      encodedField.setStaticValue(new DexValueString(replacement));
     }
   }
 
-  private void replaceIdentifierNameStringInMethod(DexEncodedMethod encodedMethod) {
+  private void replaceDexItemBasedConstStringInMethod(DexEncodedMethod encodedMethod) {
     if (!encodedMethod.getOptimizationInfo().useIdentifierNameString()) {
       return;
     }
@@ -159,8 +162,15 @@
         Instruction instruction = instructions[i];
         if (instruction instanceof DexItemBasedConstString) {
           DexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-          instructions[i] =
-              new ConstString(cnst.AA, lens.lookupName(cnst.getItem(), appInfo.dexItemFactory));
+          DexString replacement =
+              cnst.getClassNameComputationInfo().needsToComputeClassName()
+                  ? appInfo.dexItemFactory.createString(
+                      computeClassName(
+                          lens.lookupDescriptor(cnst.getItem().asDexType()),
+                          appInfo.definitionFor(cnst.getItem().asDexType()),
+                          cnst.getClassNameComputationInfo()))
+                  : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
+          instructions[i] = new ConstString(cnst.AA, replacement);
         }
       }
     } else {
@@ -170,8 +180,15 @@
         CfInstruction instruction = instructions.get(i);
         if (instruction.isDexItemBasedConstString()) {
           CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-          instructions.set(
-              i, new CfConstString(lens.lookupName(cnst.getItem(), appInfo.dexItemFactory)));
+          DexString replacement =
+              cnst.getClassNameComputationInfo().needsToComputeClassName()
+                  ? appInfo.dexItemFactory.createString(
+                      computeClassName(
+                          lens.lookupDescriptor(cnst.getItem().asDexType()),
+                          appInfo.definitionFor(cnst.getItem().asDexType()),
+                          cnst.getClassNameComputationInfo()))
+                  : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
+          instructions.set(i, new CfConstString(replacement));
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index fc10fe9..1084322 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1392,6 +1392,7 @@
           ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
           enqueueRootItems(consequentRootSet.noShrinking);
           rootSet.neverInline.addAll(consequentRootSet.neverInline);
+          rootSet.neverClassInline.addAll(consequentRootSet.neverClassInline);
           rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
           rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
           rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 3011951..8d68d2e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -87,6 +88,24 @@
         subsequentRule.materialize());
   }
 
+  protected ClassInlineRule neverClassInlineRuleForCondition() {
+    return new ClassInlineRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
+        getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        ImmutableList.of(),
+        ClassInlineRule.Type.NEVER);
+  }
+
   /**
    * Consider the following rule, which requests that class Y should be kept if the method X.m() is
    * in the final output.
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index c59081e..de0537f 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -327,7 +327,12 @@
         application.timing.end();
       }
       return new ConsequentRootSet(
-          neverInline, noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+          neverInline,
+          neverClassInline,
+          noShrinking,
+          noOptimization,
+          noObfuscation,
+          dependentNoShrinking);
     }
 
     /**
@@ -413,6 +418,14 @@
     private void materializeIfRule(ProguardIfRule rule) {
       ProguardIfRule materializedRule = rule.materialize();
 
+      // We need to abort class inlining of classes that could be matched by the condition of this
+      // -if rule.
+      ClassInlineRule neverClassInlineRuleForCondition =
+          materializedRule.neverClassInlineRuleForCondition();
+      if (neverClassInlineRuleForCondition != null) {
+        runPerRule(executorService, futures, neverClassInlineRuleForCondition, materializedRule);
+      }
+
       // If the condition of the -if rule has any members, then we need to keep these members to
       // ensure that the subsequent rule will be applied again in the second round of tree
       // shaking.
@@ -957,7 +970,7 @@
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
       this.forceInline = Collections.unmodifiableSet(forceInline);
       this.neverInline = neverInline;
-      this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
+      this.neverClassInline = neverClassInline;
       this.neverMerge = Collections.unmodifiableSet(neverMerge);
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
@@ -1010,6 +1023,7 @@
   // A partial RootSet that becomes live due to the enabled -if rule.
   static class ConsequentRootSet {
     final Set<DexMethod> neverInline;
+    final Set<DexType> neverClassInline;
     final Map<DexDefinition, ProguardKeepRule> noShrinking;
     final Set<DexDefinition> noOptimization;
     final Set<DexDefinition> noObfuscation;
@@ -1017,11 +1031,13 @@
 
     private ConsequentRootSet(
         Set<DexMethod> neverInline,
+        Set<DexType> neverClassInline,
         Map<DexDefinition, ProguardKeepRule> noShrinking,
         Set<DexDefinition> noOptimization,
         Set<DexDefinition> noObfuscation,
         Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
       this.neverInline = Collections.unmodifiableSet(neverInline);
+      this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 8fa85bf..ee11bcf 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.nio.file.FileSystemException;
 import java.nio.file.Paths;
 import java.util.function.Consumer;
@@ -65,6 +67,13 @@
       } catch (ResourceException e) {
         throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
       } catch (AssertionError e) {
+        // Most of our assertions don't have a message, create a wrapper that has the stack as the
+        // message.
+        if (e.getMessage() == null) {
+          StringWriter stack = new StringWriter();
+          e.printStackTrace(new PrintWriter(stack));
+          e = new AssertionError(stack.toString(), e);
+        }
         throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()), e);
       }
       reporter.failIfPendingErrors();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index bd0f8fb..dfe22c7 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import java.util.Map;
 import java.util.function.BiConsumer;
 import org.junit.Test;
@@ -50,7 +51,7 @@
     DexEncodedMethod foo = codeInspector.clazz(mainClass.getName()).method(signature).getMethod();
     IRCode irCode =
         foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
-    NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
+    NonNullTracker nonNullTracker = new NonNullTracker(appInfo, ImmutableSet.of());
     nonNullTracker.addNonNull(irCode);
     TypeAnalysis analysis = new TypeAnalysis(appInfo, foo);
     analysis.widening(foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index e569ad2..0ada569 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
 import java.util.function.Consumer;
 import org.junit.Test;
 
@@ -41,7 +42,7 @@
         foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     checkCountOfNonNull(irCode, 0);
 
-    NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
+    NonNullTracker nonNullTracker = new NonNullTracker(appInfo, ImmutableSet.of());
 
     nonNullTracker.addNonNull(irCode);
     assertTrue(irCode.isConsistentSSA());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 4e6bb21..592aeae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -12,7 +12,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
@@ -38,6 +41,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -46,9 +50,9 @@
 import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Iterator;
@@ -73,39 +77,35 @@
     this.backend = backend;
   }
 
-  private String run(AndroidApp app, Class mainClass) throws IOException {
-    if (backend == Backend.DEX) {
-      return runOnArt(app, mainClass);
-    } else {
-      assert backend == Backend.CF;
-      return runOnJava(app, mainClass);
-    }
-  }
-
   @Test
   public void testTrivial() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(TrivialTestClass.class),
-        ToolHelper.getClassAsBytes(TrivialTestClass.Inner.class),
-        ToolHelper.getClassAsBytes(ReferencedFields.class),
-        ToolHelper.getClassAsBytes(EmptyClass.class),
-        ToolHelper.getClassAsBytes(EmptyClassWithInitializer.class),
-        ToolHelper.getClassAsBytes(Iface1.class),
-        ToolHelper.getClassAsBytes(Iface1Impl.class),
-        ToolHelper.getClassAsBytes(Iface2.class),
-        ToolHelper.getClassAsBytes(Iface2Impl.class),
-        ToolHelper.getClassAsBytes(CycleReferenceAB.class),
-        ToolHelper.getClassAsBytes(CycleReferenceBA.class),
-        ToolHelper.getClassAsBytes(ClassWithFinal.class)
+    Class<?> main = TrivialTestClass.class;
+    Class<?>[] classes = {
+        TrivialTestClass.class,
+        TrivialTestClass.Inner.class,
+        ReferencedFields.class,
+        EmptyClass.class,
+        EmptyClassWithInitializer.class,
+        Iface1.class,
+        Iface1Impl.class,
+        Iface2.class,
+        Iface2Impl.class,
+        CycleReferenceAB.class,
+        CycleReferenceBA.class,
+        ClassWithFinal.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(TrivialTestClass.class);
-    String output = run(app, TrivialTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
@@ -162,28 +162,44 @@
 
   @Test
   public void testBuilders() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(BuildersTestClass.class),
-        ToolHelper.getClassAsBytes(BuildersTestClass.Pos.class),
-        ToolHelper.getClassAsBytes(Tuple.class),
-        ToolHelper.getClassAsBytes(Pair.class),
-        ToolHelper.getClassAsBytes(PairBuilder.class),
-        ToolHelper.getClassAsBytes(ControlFlow.class),
+    Class<?> main = BuildersTestClass.class;
+    Class<?>[] classes = {
+        NeverInline.class,
+        BuildersTestClass.class,
+        BuildersTestClass.Pos.class,
+        Tuple.class,
+        Pair.class,
+        PairBuilder.class,
+        ControlFlow.class,
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), BuildersTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .enableProguardTestOptions()
+        .enableInliningAnnotations()
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(BuildersTestClass.class);
-    String output = run(app, BuildersTestClass.class);
-    assertEquals(javaOutput, output);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
-
-    assertEquals(
-        Sets.newHashSet(
-            "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
-            "java.lang.StringBuilder"),
-        collectTypes(clazz, "testSimpleBuilder", "void"));
+    for (int i = 1; i <= 3; i++) {
+      Set<String> expected =
+          Sets.newHashSet(
+              "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
+              "java.lang.StringBuilder");
+      if (backend == Backend.CF && i < 3) {
+        // const-string canonicalization is disabled in CF, which helps ClassInliner identify
+        // PairBuilder as candidate. Concatenated builder calls in test #3 bother that again.
+        expected.add("com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder");
+      }
+      assertEquals(expected,
+          collectTypes(clazz, "testSimpleBuilder" + i, "void"));
+    }
 
     // Note that Pair created instances were also inlined in the following method since
     // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
@@ -193,7 +209,9 @@
         Collections.singleton("java.lang.StringBuilder"),
         collectTypes(clazz, "testSimpleBuilderWithMultipleBuilds", "void"));
 
-    assertFalse(inspector.clazz(PairBuilder.class).isPresent());
+    if (backend == Backend.DEX) {
+      assertFalse(inspector.clazz(PairBuilder.class).isPresent());
+    }
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
@@ -242,7 +260,6 @@
         runOnJavaRaw(mainClass.name, builder.buildClasses().toArray(new byte[2][]));
     assertThat(javaResult.stderr, containsString("IncompatibleClassChangeError"));
 
-    assert backend == Backend.DEX || backend == Backend.CF;
     // Check that the code fails with an IncompatibleClassChangeError with ART.
     ProcessResult result =
         backend == Backend.DEX
@@ -253,19 +270,24 @@
 
   @Test
   public void testCodeSample() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(C.class),
-        ToolHelper.getClassAsBytes(C.L.class),
-        ToolHelper.getClassAsBytes(C.F.class),
-        ToolHelper.getClassAsBytes(CodeTestClass.class)
+    Class<?> main = CodeTestClass.class;
+    Class<?>[] classes = {
+        C.class,
+        C.L.class,
+        C.F.class,
+        CodeTestClass.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), CodeTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(CodeTestClass.class);
-    String output = run(app, CodeTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
+    CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(C.class);
 
     assertEquals(
@@ -287,22 +309,26 @@
   @Test
   public void testInvalidatedRoot() throws Exception {
     String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
-
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+    Class<?> main = InvalidRootsTestClass.class;
+    Class<?>[] classes = {
+        InvalidRootsTestClass.class,
+        InvalidRootsTestClass.A.class,
+        InvalidRootsTestClass.B.class,
+        InvalidRootsTestClass.NeverReturnsNormally.class,
+        InvalidRootsTestClass.InitNeverReturnsNormally.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(InvalidRootsTestClass.class);
-    String output = run(app, InvalidRootsTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
@@ -331,20 +357,25 @@
 
   @Test
   public void testDesugaredLambdas() throws Exception {
-    Assume.assumeFalse(backend == Backend.CF); // No desugaring with CF backend.
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(LambdasTestClass.class),
-        ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
-        ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+    Assume.assumeFalse("No desugaring with CF backend", backend == Backend.CF);
+    Class<?> main = LambdasTestClass.class;
+    Class<?>[] classes = {
+        LambdasTestClass.class,
+        LambdasTestClass.Iface.class,
+        LambdasTestClass.IfaceUtil.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(LambdasTestClass.class);
-    String output = run(app, LambdasTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Sets.newHashSet(
@@ -391,42 +422,6 @@
         .map(fais -> fais.holder().toString());
   }
 
-  private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
-    AndroidApp compiled =
-        compileWithR8(
-            app, getProguardConfig(mainClass.getCanonicalName()), this::configure, backend);
-
-    // Materialize file for execution.
-    Path generatedFile = temp.getRoot().toPath().resolve("classes.jar");
-    compiled.writeToZip(generatedFile, outputMode(backend));
-
-    assert backend == Backend.DEX || backend == Backend.CF;
-
-    String output =
-        backend == Backend.DEX
-            ? ToolHelper.runArtNoVerificationErrors(
-                generatedFile.toString(), mainClass.getCanonicalName())
-            : ToolHelper.runJava(generatedFile, mainClass.getCanonicalName()).stdout;
-
-    // Compare with Java.
-    ToolHelper.ProcessResult javaResult = ToolHelper.runJava(
-        ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
-
-    if (javaResult.exitCode != 0) {
-      System.out.println(javaResult.stdout);
-      System.err.println(javaResult.stderr);
-      fail("JVM on original program failed for: " + mainClass);
-    }
-    assertEquals(
-        backend == Backend.DEX
-            ? "JVM and ART output differ."
-            : "Output of original and processed program differ on JVM.",
-        javaResult.stdout,
-        output);
-
-    return compiled;
-  }
-
   private String getProguardConfig(String main) {
     return StringUtils.joinLines(
         keepMainProguardConfiguration(main),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
index 1516e14..aa6d4dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.builders;
 
+import com.android.tools.r8.NeverInline;
+
 public class BuildersTestClass {
   private static int ID = 0;
 
@@ -17,31 +19,35 @@
 
   public static void main(String[] args) {
     BuildersTestClass test = new BuildersTestClass();
-    test.testSimpleBuilder();
+    test.testSimpleBuilder1();
     test.testSimpleBuilderWithMultipleBuilds();
     test.testBuilderConstructors();
     test.testWithControlFlow();
     test.testWithMoreControlFlow();
   }
 
-  private synchronized void testSimpleBuilder() {
+  @NeverInline
+  private void testSimpleBuilder1() {
     System.out.println(
-        new PairBuilder<String, String>().setFirst("f-" + next()).build().toString());
+        new PairBuilder<String, String>().setFirst("f-" + next()).build());
     testSimpleBuilder2();
     testSimpleBuilder3();
   }
 
-  private synchronized void testSimpleBuilder2() {
+  @NeverInline
+  private void testSimpleBuilder2() {
     System.out.println(
-        new PairBuilder<String, String>().setSecond("s-" + next()).build().toString());
+        new PairBuilder<String, String>().setSecond("s-" + next()).build());
   }
 
-  private synchronized void testSimpleBuilder3() {
+  @NeverInline
+  private void testSimpleBuilder3() {
     System.out.println(new PairBuilder<String, String>()
-        .setFirst("f-" + next()).setSecond("s-" + next()).build().toString());
+        .setFirst("f-" + next()).setSecond("s-" + next()).build());
   }
 
-  private synchronized void testSimpleBuilderWithMultipleBuilds() {
+  @NeverInline
+  private void testSimpleBuilderWithMultipleBuilds() {
     PairBuilder<String, String> builder = new PairBuilder<>();
     Pair p1 = builder.build();
     System.out.println(p1.toString());
@@ -53,13 +59,15 @@
     System.out.println(p3.toString());
   }
 
-  private synchronized void testBuilderConstructors() {
+  @NeverInline
+  private void testBuilderConstructors() {
     System.out.println(new Tuple().toString());
     System.out.println(new Tuple(true, (byte) 77, (short) 9977, '#', 42,
         987654321123456789L, -12.34f, 43210.98765, "s-" + next() + "-s").toString());
   }
 
-  private synchronized void testWithControlFlow() {
+  @NeverInline
+  private void testWithControlFlow() {
     ControlFlow flow = new ControlFlow(-1, 2, 7);
     for (int k = 0; k < 25; k++) {
       if (k % 3 == 0) {
@@ -71,7 +79,8 @@
     System.out.println("flow = " + flow.toString());
   }
 
-  private synchronized void testWithMoreControlFlow() {
+  @NeverInline
+  private void testWithMoreControlFlow() {
     String str = "1234567890";
     Pos pos = new Pos();
     while (pos.y < str.length()) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index f078262..6fea6cd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -134,6 +134,6 @@
         inspector.clazz(TestClass.class).method("void", "bar", ImmutableList.of());
     Iterator<InstructionSubject> barInstructionIterator =
         barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
-    assertEquals(6, Streams.stream(barInstructionIterator).count());
+    assertEquals(4, Streams.stream(barInstructionIterator).count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 09ab7f7..99cce0b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -153,6 +153,53 @@
       // getSimpleName, local
       "-Returned-empty-"
   );
+  private static final String RENAMED_OUTPUT = StringUtils.lines(
+      // getName
+      "com.android.tools.r8.ir.optimize.reflection.e",
+      // getTypeName
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.e",
+      // getCanonicalName
+      "com.android.tools.r8.ir.optimize.reflection.e",
+      // getSimpleName
+      "e",
+      // getName, inner
+      "com.android.tools.r8.ir.optimize.reflection.c",
+      // getTypeName, inner
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.c",
+      // getCanonicalName, inner
+      "com.android.tools.r8.ir.optimize.reflection.c",
+      // getSimpleName, inner
+      "c",
+      // getName, array
+      "[Lcom.android.tools.r8.ir.optimize.reflection.e;",
+      // getTypeName, array
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.e[]",
+      // getCanonicalName, array
+      "com.android.tools.r8.ir.optimize.reflection.e[]",
+      // getSimpleName, array
+      "e[]",
+      // getName, anonymous
+      "com.android.tools.r8.ir.optimize.reflection.a",
+      // getTypeName, anonymous
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.a",
+      // getCanonicalName, anonymous
+      "-Returned-null-",
+      // getSimpleName, anonymous
+      "-Returned-empty-",
+      // getName, local
+      "com.android.tools.r8.ir.optimize.reflection.b",
+      // getTypeName, local
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.b,
+      // getCanonicalName, local
+      "-Returned-null-",
+      // getSimpleName, local
+      "-Returned-empty-"
+  );
   private static final Class<?> MAIN = GetName0Main.class;
 
   public GetNameTest(Backend backend, boolean enableMinification) throws Exception {
@@ -212,18 +259,13 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep class **.GetName0*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
-    TestRunResult result = builder.run(MAIN);
-    if (enableMinification) {
-      // TODO(b/118536394): Check even renamed names.
-      test(result, 11);
-    } else {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-      test(result, 0);
-    }
+    TestRunResult result = builder.run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 0);
   }
 
   @Test
@@ -235,7 +277,8 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep,allowobfuscation class **.GetName0*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
@@ -245,12 +288,11 @@
       if (backend == Backend.CF) {
         return;
       }
-      // TODO(b/118536394): Check even renamed names.
-      test(result, 11);
+      result.assertSuccessWithOutput(RENAMED_OUTPUT);
     } else {
       result.assertSuccessWithOutput(JAVA_OUTPUT);
-      test(result, 0);
     }
+    test(result, 0);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
index ef7a07b..22918c8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
@@ -4,10 +4,13 @@
 package com.android.tools.r8.ir.optimize.reflection;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.Streams;
+import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Collection;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -19,6 +22,7 @@
 
   final Backend backend;
   final boolean enableMinification;
+  Path mapping;
 
   @Parameterized.Parameters(name = "Backend: {0} minification: {1}")
   public static Collection<Object[]> data() {
@@ -30,6 +34,11 @@
     this.enableMinification = enableMinification;
   }
 
+  Path createNewMappingPath() throws IOException {
+    mapping = temp.newFile(ToolHelper.DEFAULT_PROGUARD_MAP_FILE).toPath();
+    return mapping;
+  }
+
   static boolean isNameReflection(DexMethod method) {
     return method.getHolder().toDescriptorString().equals(CLASS_DESCRIPTOR)
         && method.getArity() == 0
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index 54015e3..5bc07da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -88,6 +88,14 @@
       "Local[][][]",
       "[][][]"
   );
+  private static final String RENAMED_OUTPUT = StringUtils.lines(
+      "f",
+      "e",
+      "b",
+      "a",
+      "d[][][]",
+      "[][][]"
+  );
   private static final Class<?> MAIN = ClassGetSimpleName.class;
 
   public GetSimpleNameTest(Backend backend, boolean enableMinification) throws Exception {
@@ -146,16 +154,12 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep class **.ClassGetSimpleName*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
-    TestRunResult result = builder.run(MAIN);
-    if (enableMinification) {
-      // TODO(b/118536394): Check even renamed simple name.
-    } else {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-    }
+    TestRunResult result = builder.run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 0);
   }
 
@@ -168,7 +172,8 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
@@ -178,7 +183,7 @@
       if (backend == Backend.CF) {
         return;
       }
-      // TODO(b/118536394): Check even renamed simple name.
+      result.assertSuccessWithOutput(RENAMED_OUTPUT);
     } else {
       result.assertSuccessWithOutput(JAVA_OUTPUT);
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index 353dd50..284af11 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -30,7 +30,10 @@
   static boolean argCouldBeNull(String arg) {
     return "CONST".contains(arg)
         && "prefix".startsWith(arg)
-        && "suffix".endsWith(arg);
+        && "suffix".endsWith(arg)
+        && "CONST".equals(arg)
+        && "CONST".equalsIgnoreCase(arg)
+        && "CONST".contentEquals(arg);
   }
 
   public static void main(String[] args) {
@@ -39,6 +42,10 @@
       System.out.println(s1.contains("CONST"));
       System.out.println(s1.startsWith("prefix"));
       System.out.println(s1.endsWith("suffix"));
+      System.out.println(s1.equals("prefix-CONST-suffix"));
+      System.out.println(s1.equalsIgnoreCase("PREFIX-const-SUFFIX"));
+      System.out.println(s1.contentEquals("prefix-CONST-suffix"));
+      System.out.println(s1.contentEquals(new StringBuffer("prefix-CONST-suffix")));
     }
 
     {
@@ -47,6 +54,10 @@
       System.out.println(s2.contains("CONST"));
       System.out.println(s2.startsWith("prefix"));
       System.out.println(s2.endsWith("suffix"));
+      System.out.println(s2.equals("prefix-CONST-suffix"));
+      System.out.println(s2.equalsIgnoreCase("pre-con-suf"));
+      System.out.println(s2.contentEquals("prefix-CONST-suffix"));
+      System.out.println(s2.contentEquals(new StringBuffer("prefix-CONST-suffix")));
     }
 
     {
@@ -75,12 +86,28 @@
       "true",
       // s1, endsWith
       "true",
+      // s1, equals
+      "true",
+      // s1, equalsIgnoreCase
+      "true",
+      // s1, contentEquals(CharSequence)
+      "true",
+      // s1, contentEquals(StringBuffer)
+      "true",
       // s2, contains
       "false",
       // s2, startsWith
       "false",
       // s2, endsWith
       "false",
+      // s2, equals
+      "false",
+      // s2, equalsIgnoreCase
+      "false",
+      // s2, contentEquals(CharSequence)
+      "false",
+      // s2, contentEquals(StringBuffer)
+      "false",
       // argCouldBeNull
       "false"
   );
@@ -107,7 +134,10 @@
         && method.proto.returnType.isBooleanType()
         && (method.name.toString().equals("contains")
             || method.name.toString().equals("startsWith")
-            || method.name.toString().equals("endsWith"));
+            || method.name.toString().equals("endsWith")
+            || method.name.toString().equals("equals")
+            || method.name.toString().equals("equalsIgnoreCase")
+            || method.name.toString().equals("contentEquals"));
   }
 
   private long countStringContentChecker(MethodSubject method) {
@@ -131,7 +161,7 @@
         "boolean", "argCouldBeNull", ImmutableList.of("java.lang.String"));
     assertThat(argCouldBeNull, isPresent());
     // Because of nullable argument, all checkers should remain.
-    assertEquals(3, countStringContentChecker(argCouldBeNull));
+    assertEquals(6, countStringContentChecker(argCouldBeNull));
   }
 
   @Test
@@ -143,14 +173,14 @@
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 6);
+    test(result, 14);
 
     result = testForD8()
         .release()
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 3);
+    test(result, 8);
   }
 
   @Test
@@ -162,6 +192,6 @@
         .addKeepMainRule(MAIN)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 3);
+    test(result, 8);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 6642619..3f579c7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -54,13 +54,9 @@
 
   public static void main(String[] args) {
     Foo foo = new Foo();
-    // TODO(b/118536394): valueOf in getter() can be removed if combined with name reflection
-    // optimization, which will replace it with (definitely non-null) const-string.
     System.out.println(foo.getter());
     // Trivial, it's String.
     String str = foo.toString();
-    // TODO(b/119449728): But, it's still nullable.
-    // valueOf can be removed if the nullability of its return value is modeled.
     System.out.println(String.valueOf(str));
     if (str != null) {
       // With an explicit check, it's non-null String.
@@ -195,6 +191,6 @@
         .addKeepRules("-dontobfuscate")
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 3, 1, 1);
+    test(result, 1, 1, 1);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java
new file mode 100644
index 0000000..caa06d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.classinlining;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfRuleWithClassInlining extends TestBase {
+
+  private final Backend backend;
+  private final boolean enableClassInlining;
+  private final boolean enableIfRule;
+
+  public IfRuleWithClassInlining(
+      Backend backend, boolean enableClassInlining, boolean enableIfRule) {
+    this.backend = backend;
+    this.enableClassInlining = enableClassInlining;
+    this.enableIfRule = enableIfRule;
+  }
+
+  @Parameters(name = "Backend: {0}, class inlining: {1}, with if rule: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(Backend.values(), BooleanUtils.values(), BooleanUtils.values());
+  }
+
+  @Test
+  public void r8Test() throws Exception {
+    String ifRule =
+        StringUtils.lines(
+            "-if class " + StringBox.Builder.class.getTypeName(),
+            "-keep class " + Unused.class.getTypeName());
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(IfRuleWithClassInlining.class)
+            .addKeepMainRule(TestClass.class)
+            // TODO(b/120061431): Should not be needed for this example.
+            .addKeepRules("-allowaccessmodification")
+            .addKeepRules(enableIfRule ? ifRule : "")
+            .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
+            .compile()
+            .inspector();
+    if (enableIfRule || !enableClassInlining) {
+      assertThat(inspector.clazz(StringBox.Builder.class), isPresent());
+    } else {
+      assertThat(inspector.clazz(StringBox.Builder.class), not(isPresent()));
+    }
+    if (enableIfRule) {
+      assertThat(inspector.clazz(Unused.class), isPresent());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      StringBox box = StringBox.builder().setString("Hello world").build();
+      System.out.println(box.getString());
+    }
+  }
+
+  static class StringBox {
+
+    static class Builder {
+
+      private String string = null;
+
+      public Builder setString(String string) {
+        this.string = string;
+        return this;
+      }
+
+      public StringBox build() {
+        return new StringBox(string);
+      }
+    }
+
+    private final String string;
+
+    public StringBox(String string) {
+      this.string = string;
+    }
+
+    public String getString() {
+      return string;
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+  }
+
+  static class Unused {}
+}