Merge commit 'd6d30aa0fa15ac2771972a55d0fd8e86d3c1a251' into dev-release

Change-Id: I5a7c07ae3c1678f255e6875613024d5d83505f68
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index a081d34..4869bc5 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -188,7 +188,7 @@
     while (iterator.hasNext()) {
       result = result.join(toTypeElement(iterator.next()), appView);
     }
-    // All types are reference types so the join is either a class or an array.
+    // All types are reference types so the join is either a class, an interface or an array.
     if (result.isClassType()) {
       ClassTypeElement classType = result.asClassType();
       if (classType.getClassType().isIdenticalTo(appView.dexItemFactory().objectType)
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index c16eae4..9936ef6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -31,9 +31,10 @@
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
-import java.util.ArrayList;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.function.Supplier;
 import org.objectweb.asm.ConstantDynamic;
 import org.objectweb.asm.MethodVisitor;
 
@@ -41,22 +42,8 @@
 
   private final ConstantDynamicReference reference;
 
-  public CfConstDynamic(
-      int symbolicReferenceId,
-      DexString name,
-      DexType type,
-      DexMethodHandle bootstrapMethod,
-      List<DexValue> bootstrapMethodArguments) {
-    assert symbolicReferenceId >= 0;
-    assert name != null;
-    assert type != null;
-    assert bootstrapMethod != null;
-    assert bootstrapMethodArguments != null;
-    assert bootstrapMethodArguments.isEmpty();
-
-    reference =
-        new ConstantDynamicReference(
-            symbolicReferenceId, name, type, bootstrapMethod, bootstrapMethodArguments);
+  public CfConstDynamic(ConstantDynamicReference constantDynamicReference) {
+    reference = constantDynamicReference;
   }
 
   @Override
@@ -86,27 +73,14 @@
   }
 
   public static CfConstDynamic fromAsmConstantDynamic(
-      int symbolicReferenceId,
       ConstantDynamic insn,
       JarApplicationReader application,
-      DexType clazz) {
-    String constantName = insn.getName();
-    String constantDescriptor = insn.getDescriptor();
-    DexMethodHandle bootstrapMethodHandle =
-        DexMethodHandle.fromAsmHandle(insn.getBootstrapMethod(), application, clazz);
-    int argumentCount = insn.getBootstrapMethodArgumentCount();
-    List<DexValue> bootstrapMethodArguments = new ArrayList<>(argumentCount);
-    for (int i = 0; i < argumentCount; i++) {
-      Object argument = insn.getBootstrapMethodArgument(i);
-      DexValue dexValue = DexValue.fromAsmBootstrapArgument(argument, application, clazz);
-      bootstrapMethodArguments.add(dexValue);
-    }
+      DexType clazz,
+      Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
+    assert insn.getBootstrapMethodArgumentCount() == 0;
     return new CfConstDynamic(
-        symbolicReferenceId,
-        application.getString(constantName),
-        application.getTypeFromDescriptor(constantDescriptor),
-        bootstrapMethodHandle,
-        bootstrapMethodArguments);
+        ConstantDynamicReference.fromAsmConstantDynamic(
+            insn, application, clazz, constantDynamicSymbolicReferencesSupplier));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
index d1b3d3e..86ea3c0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -248,29 +248,32 @@
     // safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
     // by replacing the operand stack with a stack whose sole element is the handler's
     // exception class.
-    for (CfLabel target : tryCatchRange.getTargets()) {
+    List<CfLabel> targets = tryCatchRange.getTargets();
+    for (int i = 0; i < targets.size(); i++) {
+      CfLabel target = targets.get(i);
+      DexType guard = tryCatchRange.guards.get(i);
       CfFrame destinationFrame = labelToFrameMap.get(target);
       if (destinationFrame == null) {
         return CfCodeStackMapValidatingException.invalidTryCatchRange(
             method, tryCatchRange, "No frame for target catch range target", appView);
       }
-      // From the spec: the handler's exception class is assignable to the class Throwable.
-      for (DexType guard : tryCatchRange.guards) {
-        if (!config.getAssignability().isAssignable(guard, factory.throwableType)) {
-          return CfCodeStackMapValidatingException.invalidTryCatchRange(
-              method,
-              tryCatchRange,
-              "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
-              appView);
-        }
-        Deque<PreciseFrameType> sourceStack =
-            ImmutableDeque.of(FrameType.initializedNonNullReference(guard));
-        AssignabilityResult assignabilityResult =
-            config.getAssignability().isStackAssignable(sourceStack, destinationFrame.getStack());
-        if (assignabilityResult.isFailed()) {
-          return CfCodeStackMapValidatingException.invalidTryCatchRange(
-              method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
-        }
+      Deque<PreciseFrameType> sourceStack =
+          ImmutableDeque.of(FrameType.initializedNonNullReference(guard));
+      AssignabilityResult assignabilityResult =
+          config.getAssignability().isStackAssignable(sourceStack, destinationFrame.getStack());
+      if (assignabilityResult.isFailed()) {
+        return CfCodeStackMapValidatingException.invalidTryCatchRange(
+            method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
+      }
+    }
+    // From the spec: the handler's exception class is assignable to the class Throwable.
+    for (DexType guard : tryCatchRange.guards) {
+      if (!config.getAssignability().isAssignable(guard, factory.throwableType)) {
+        return CfCodeStackMapValidatingException.invalidTryCatchRange(
+            method,
+            tryCatchRange,
+            "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
+            appView);
       }
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index c09e32a..2cf72f1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.io.BaseEncoding;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectOutputStream;
@@ -24,9 +25,10 @@
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Supplier;
+import org.objectweb.asm.ConstantDynamic;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.InvokeDynamicInsnNode;
 
 public final class DexCallSite extends IndexedDexItem
     implements StructuralItem<DexCallSite>, LirConstant {
@@ -76,17 +78,13 @@
   }
 
   public static DexCallSite fromAsmInvokeDynamic(
-      InvokeDynamicInsnNode insn, JarApplicationReader application, DexType clazz) {
-    return fromAsmInvokeDynamic(application, clazz, insn.name, insn.desc, insn.bsm, insn.bsmArgs);
-  }
-
-  public static DexCallSite fromAsmInvokeDynamic(
       JarApplicationReader application,
       DexType clazz,
       String name,
       String desc,
       Handle bsmHandle,
-      Object[] bsmArgs) {
+      Object[] bsmArgs,
+      Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
     // Bootstrap method
     if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC
         && bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
@@ -99,7 +97,9 @@
     // Decode static bootstrap arguments
     List<DexValue> bootstrapArgs = new ArrayList<>();
     for (Object arg : bsmArgs) {
-      bootstrapArgs.add(DexValue.fromAsmBootstrapArgument(arg, application, clazz));
+      bootstrapArgs.add(
+          DexValue.fromAsmBootstrapArgument(
+              arg, application, clazz, constantDynamicSymbolicReferencesSupplier));
     }
 
     // Construct call site
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 38b5634..f4f7a60 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.dex.FileWriter;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.EncodedValueUtils;
@@ -26,8 +28,11 @@
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.Arrays;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.objectweb.asm.ConstantDynamic;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Type;
 
@@ -51,7 +56,8 @@
     ARRAY(0x1c),
     ANNOTATION(0x1d),
     NULL(0x1e),
-    BOOLEAN(0x1f);
+    BOOLEAN(0x1f),
+    CONST_DYNAMIC(-1);
 
     public static DexValueKind fromId(int id) {
       switch (id) {
@@ -91,6 +97,8 @@
           return NULL;
         case 0x1f:
           return BOOLEAN;
+        case -1:
+          return CONST_DYNAMIC;
         default:
           throw new Unreachable();
       }
@@ -162,6 +170,14 @@
     return null;
   }
 
+  public boolean isDexValueConstDynamic() {
+    return false;
+  }
+
+  public DexValueConstDynamic asDexValueConstDynamic() {
+    return null;
+  }
+
   public boolean isDexValueMethodType() {
     return false;
   }
@@ -313,7 +329,10 @@
   public abstract AbstractValue toAbstractValue(AbstractValueFactory factory);
 
   public static DexValue fromAsmBootstrapArgument(
-      Object value, JarApplicationReader application, DexType clazz) {
+      Object value,
+      JarApplicationReader application,
+      DexType clazz,
+      Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
     if (value instanceof Integer) {
       return DexValue.DexValueInt.create((Integer) value);
     } else if (value instanceof Long) {
@@ -334,12 +353,23 @@
         case Type.METHOD:
           return new DexValue.DexValueMethodType(
               application.getProto(((Type) value).getDescriptor()));
+        case Type.ARRAY:
+          DexType arrayType = application.getTypeFromDescriptor(((Type) value).getDescriptor());
+          assert arrayType.isArrayType();
+          return new DexValue.DexValueType(arrayType);
         default:
           throw new Unreachable("Type sort is not supported: " + type.getSort());
       }
     } else if (value instanceof Handle) {
       return new DexValue.DexValueMethodHandle(
           DexMethodHandle.fromAsmHandle((Handle) value, application, clazz));
+    } else if (value instanceof ConstantDynamic) {
+      return new DexValue.DexValueConstDynamic(
+          ConstantDynamicReference.fromAsmConstantDynamic(
+              (ConstantDynamic) value,
+              application,
+              clazz,
+              constantDynamicSymbolicReferencesSupplier));
     } else {
       throw new Unreachable(
           "Unsupported bootstrap static argument of type " + value.getClass().getSimpleName());
@@ -2054,4 +2084,96 @@
       return UnknownValue.getInstance();
     }
   }
+
+  public static class DexValueConstDynamic extends DexValue {
+
+    private final ConstantDynamicReference value;
+
+    public DexValueConstDynamic(ConstantDynamicReference value) {
+      this.value = value;
+    }
+
+    @Override
+    public boolean isDexValueConstDynamic() {
+      return true;
+    }
+
+    @Override
+    public DexValueConstDynamic asDexValueConstDynamic() {
+      return this;
+    }
+
+    @Override
+    int internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      return value.acceptCompareTo(other.asDexValueConstDynamic().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.CONST_DYNAMIC;
+    }
+
+    private CompilationError throwCannotConvertToDex() {
+      throw new CompilationError("DexValueConstDynamic should be desugared");
+    }
+
+    @Override
+    public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
+      throw throwCannotConvertToDex();
+    }
+
+    @Override
+    public void sort() {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
+      throw throwCannotConvertToDex();
+    }
+
+    @Override
+    public int hashCode() {
+      return 7 * value.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (other instanceof DexValueConstDynamic) {
+        DexValueConstDynamic otherCstDynamic = (DexValueConstDynamic) other;
+        return otherCstDynamic.value.equals(value);
+      }
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return "Item " + getValueKind() + " " + value;
+    }
+
+    @Override
+    public DexType getType(DexItemFactory factory) {
+      return null;
+    }
+
+    @Override
+    public Object getBoxedValue() {
+      throw new Unreachable("No boxed value for DexValueConstDynamic");
+    }
+
+    @Override
+    public Object asAsmEncodedObject() {
+      throw new Unreachable("No ASM conversion for DexValueConstDynamic");
+    }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 2ad9817..c41f8df 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -921,7 +921,14 @@
     @Override
     public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
       DexCallSite callSite =
-          DexCallSite.fromAsmInvokeDynamic(application, method.holder, name, desc, bsm, bsmArgs);
+          DexCallSite.fromAsmInvokeDynamic(
+              application,
+              method.holder,
+              name,
+              desc,
+              bsm,
+              bsmArgs,
+              constantDynamicSymbolicReferencesSupplier);
       addInstruction(new CfInvokeDynamic(callSite));
     }
 
@@ -1027,16 +1034,12 @@
         // instance from ASM, even when they are equal (i.e. all their components are equal). See
         // ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest
         // for an example.
-        Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences =
-            constantDynamicSymbolicReferencesSupplier.get();
-        int symbolicReferenceId = constantDynamicSymbolicReferences.getOrDefault(cst, -1);
-        if (symbolicReferenceId == -1) {
-          symbolicReferenceId = constantDynamicSymbolicReferences.size();
-          constantDynamicSymbolicReferences.put((ConstantDynamic) cst, symbolicReferenceId);
-        }
         addInstruction(
             CfConstDynamic.fromAsmConstantDynamic(
-                symbolicReferenceId, (ConstantDynamic) cst, application, method.holder));
+                (ConstantDynamic) cst,
+                application,
+                method.holder,
+                constantDynamicSymbolicReferencesSupplier));
       } else {
         throw new CompilationError("Unsupported constant: " + cst.toString());
       }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
index f70748d..fa4979c 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
-import com.android.tools.r8.utils.DescriptorUtils;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.IdentityHashMap;
@@ -36,16 +35,19 @@
   private final R8ResourceShrinkerState resourceShrinkerState;
   private final Map<DexType, RClassFieldToValueStore> fieldToValueMapping = new IdentityHashMap<>();
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final Enqueuer enqueuer;
 
-  private ResourceAccessAnalysis(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  private ResourceAccessAnalysis(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     this.appView = appView;
     this.resourceShrinkerState = appView.getResourceShrinkerState();
+    this.enqueuer = enqueuer;
   }
 
   public static void register(
       AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     if (enabled(appView, enqueuer)) {
-      enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView));
+      enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
     }
   }
 
@@ -73,7 +75,7 @@
     if (resolvedField == null) {
       return;
     }
-    if (getMaybeCachedIsRClass(resolvedField.getHolder())) {
+    if (enqueuer.isRClass(resolvedField.getHolder())) {
       DexType holderType = resolvedField.getHolderType();
       if (!fieldToValueMapping.containsKey(holderType)) {
         populateRClassValues(resolvedField);
@@ -189,18 +191,6 @@
     }
   }
 
-  private final Map<DexProgramClass, Boolean> cachedClassLookups = new IdentityHashMap<>();
-
-  private boolean getMaybeCachedIsRClass(DexProgramClass holder) {
-    Boolean result = cachedClassLookups.get(holder);
-    if (result != null) {
-      return result;
-    }
-    boolean isRClass = DescriptorUtils.isRClassDescriptor(holder.getType().toDescriptorString());
-    cachedClassLookups.put(holder, isRClass);
-    return isRClass;
-  }
-
   private static class RClassFieldToValueStore {
     private Map<DexField, IntList> valueMapping;
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
index 478e4cf..2f27eab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -7,12 +7,17 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.utils.structural.Equatable;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Supplier;
+import org.objectweb.asm.ConstantDynamic;
 
 public class ConstantDynamicReference implements StructuralItem<ConstantDynamicReference> {
   private final int symbolicReferenceId;
@@ -25,13 +30,53 @@
     spec.withInt(c -> c.symbolicReferenceId);
   }
 
-  public ConstantDynamicReference(
+  public static ConstantDynamicReference fromAsmConstantDynamic(
+      ConstantDynamic cst,
+      JarApplicationReader application,
+      DexType clazz,
+      Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
+
+    Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences =
+        constantDynamicSymbolicReferencesSupplier.get();
+    int symbolicReferenceId = constantDynamicSymbolicReferences.getOrDefault(cst, -1);
+    if (symbolicReferenceId == -1) {
+      symbolicReferenceId = constantDynamicSymbolicReferences.size();
+      constantDynamicSymbolicReferences.put(cst, symbolicReferenceId);
+    }
+
+    String constantName = cst.getName();
+    String constantDescriptor = cst.getDescriptor();
+    DexMethodHandle bootstrapMethodHandle =
+        DexMethodHandle.fromAsmHandle(cst.getBootstrapMethod(), application, clazz);
+    int argumentCount = cst.getBootstrapMethodArgumentCount();
+    List<DexValue> bootstrapMethodArguments1 = new ArrayList<>(argumentCount);
+    for (int i = 0; i < argumentCount; i++) {
+      Object argument = cst.getBootstrapMethodArgument(i);
+      DexValue dexValue =
+          DexValue.fromAsmBootstrapArgument(
+              argument, application, clazz, constantDynamicSymbolicReferencesSupplier);
+      bootstrapMethodArguments1.add(dexValue);
+    }
+
+    return new ConstantDynamicReference(
+        symbolicReferenceId,
+        application.getString(constantName),
+        application.getTypeFromDescriptor(constantDescriptor),
+        bootstrapMethodHandle,
+        bootstrapMethodArguments1);
+  }
+
+  private ConstantDynamicReference(
       int symbolicReferenceId,
       DexString name,
       DexType type,
       DexMethodHandle bootstrapMethod,
       List<DexValue> bootstrapMethodArguments) {
-    assert bootstrapMethodArguments.isEmpty();
+    assert symbolicReferenceId >= 0;
+    assert name != null;
+    assert type != null;
+    assert bootstrapMethod != null;
+    assert bootstrapMethodArguments != null;
     this.symbolicReferenceId = symbolicReferenceId;
     this.name = name;
     this.type = type;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 1f95169..7ee35d8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -774,6 +774,10 @@
       return resolvedMethod.getReference();
     }
 
+    if (isMonomorphicVirtualMethod(resolvedMethod)) {
+      return resolvedMethod.getReference();
+    }
+
     if (invoke.isInvokeInterface()) {
       assert !isMonomorphicVirtualMethod(resolvedMethod);
       return getVirtualRootMethod(resolvedMethod);
@@ -781,10 +785,6 @@
 
     assert invoke.isInvokeSuper() || invoke.isInvokeVirtual();
 
-    if (isMonomorphicVirtualMethod(resolvedMethod)) {
-      return resolvedMethod.getReference();
-    }
-
     DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
     assert rootMethod != null;
     assert !isMonomorphicVirtualMethod(resolvedMethod)
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
index b5f1374..0165f8e 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
@@ -3,9 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
@@ -14,8 +13,13 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -27,58 +31,86 @@
   protected static class VirtualRootMethod {
 
     private final VirtualRootMethod parent;
-    private final ProgramMethod root;
-    private final ProgramMethodSet overrides = ProgramMethodSet.create();
+    private final ProgramMethod method;
+
+    private Set<VirtualRootMethod> overrides = Collections.emptySet();
+    private List<VirtualRootMethod> siblings = Collections.emptyList();
+    private boolean mayDispatchOutsideProgram = false;
 
     VirtualRootMethod(ProgramMethod root) {
       this(root, null);
     }
 
-    VirtualRootMethod(ProgramMethod root, VirtualRootMethod parent) {
-      assert root != null;
+    VirtualRootMethod(ProgramMethod method, VirtualRootMethod parent) {
+      assert method != null;
       this.parent = parent;
-      this.root = root;
+      this.method = method;
     }
 
-    void addOverride(ProgramMethod override) {
-      assert override.getDefinition() != root.getDefinition();
-      assert override.getMethodSignature().equals(root.getMethodSignature());
+    void addOverride(VirtualRootMethod override) {
+      assert !override.getMethod().isStructurallyEqualTo(method);
+      assert override.getMethod().getReference().match(method.getReference());
+      if (overrides.isEmpty()) {
+        overrides = Sets.newIdentityHashSet();
+      }
       overrides.add(override);
       if (hasParent()) {
         getParent().addOverride(override);
       }
     }
 
+    void addSibling(VirtualRootMethod sibling) {
+      if (siblings.isEmpty()) {
+        siblings = new ArrayList<>(1);
+      }
+      siblings.add(sibling);
+    }
+
+    void setMayDispatchOutsideProgram() {
+      mayDispatchOutsideProgram = true;
+    }
+
     boolean hasParent() {
       return parent != null;
     }
 
+    boolean hasSiblings() {
+      return !siblings.isEmpty();
+    }
+
     VirtualRootMethod getParent() {
       return parent;
     }
 
-    ProgramMethod getRoot() {
-      return root;
+    VirtualRootMethod getRoot() {
+      return hasParent() ? getParent().getRoot() : this;
     }
 
-    ProgramMethod getSingleNonAbstractMethod() {
-      ProgramMethod singleNonAbstractMethod = root.getAccessFlags().isAbstract() ? null : root;
-      for (ProgramMethod override : overrides) {
-        if (!override.getAccessFlags().isAbstract()) {
-          if (singleNonAbstractMethod != null) {
+    ProgramMethod getMethod() {
+      return method;
+    }
+
+    VirtualRootMethod getSingleDispatchTarget() {
+      assert !hasParent();
+      if (isMayDispatchOutsideProgramSet()) {
+        return null;
+      }
+      VirtualRootMethod singleDispatchTarget = isAbstract() ? null : this;
+      for (VirtualRootMethod override : overrides) {
+        if (!override.isAbstract()) {
+          if (singleDispatchTarget != null) {
             // Not a single non-abstract method.
             return null;
           }
-          singleNonAbstractMethod = override;
+          singleDispatchTarget = override;
         }
       }
-      assert singleNonAbstractMethod == null
-          || !singleNonAbstractMethod.getAccessFlags().isAbstract();
-      return singleNonAbstractMethod;
+      assert singleDispatchTarget == null || !singleDispatchTarget.isAbstract();
+      return singleDispatchTarget;
     }
 
-    void forEach(Consumer<ProgramMethod> consumer) {
-      consumer.accept(root);
+    void forEach(Consumer<VirtualRootMethod> consumer) {
+      consumer.accept(this);
       overrides.forEach(consumer);
     }
 
@@ -86,10 +118,12 @@
       return !overrides.isEmpty();
     }
 
-    boolean isInterfaceMethodWithSiblings() {
-      // TODO(b/190154391): Conservatively returns true for all interface methods, but should only
-      //  return true for those with siblings.
-      return root.getHolder().isInterface();
+    boolean isAbstract() {
+      return method.getAccessFlags().isAbstract();
+    }
+
+    boolean isMayDispatchOutsideProgramSet() {
+      return mayDispatchOutsideProgram;
     }
   }
 
@@ -122,18 +156,48 @@
           DexMethodSignatureMap<VirtualRootMethod> virtualRootMethodsForSuperclass =
               virtualRootMethodsPerClass.get(superclass);
           virtualRootMethodsForSuperclass.forEach(
-              (signature, info) ->
-                  virtualRootMethodsForClass.computeIfAbsent(
-                      signature, ignoreKey(() -> new VirtualRootMethod(info.getRoot(), info))));
+              (signature, info) -> {
+                virtualRootMethodsForClass.compute(
+                    signature,
+                    (ignore, existing) -> {
+                      if (existing == null || existing == info) {
+                        return info;
+                      } else {
+                        // We iterate the immediate supertypes in-order using
+                        // forEachImmediateProgramSuperClass. Therefore, the current method is
+                        // guaranteed to be an interface method when existing != null.
+                        assert info.getMethod().getHolder().isInterface();
+                        if (!existing.getMethod().getHolder().isInterface()) {
+                          existing.addSibling(info);
+                          info.addOverride(existing);
+                        }
+                        return existing;
+                      }
+                    });
+                if (!clazz.isInterface() && superclass.isInterface()) {
+                  DexClassAndMethod resolvedMethod =
+                      appView.appInfo().resolveMethodOnClass(clazz, signature).getResolutionPair();
+                  if (resolvedMethod != null
+                      && !resolvedMethod.isProgramMethod()
+                      && !resolvedMethod.getAccessFlags().isAbstract()) {
+                    info.setMayDispatchOutsideProgram();
+                  }
+                }
+              });
         });
     clazz.forEachProgramVirtualMethod(
-        method -> {
-          if (virtualRootMethodsForClass.containsKey(method)) {
-            virtualRootMethodsForClass.get(method).getParent().addOverride(method);
-          } else {
-            virtualRootMethodsForClass.put(method, new VirtualRootMethod(method));
-          }
-        });
+        method ->
+            virtualRootMethodsForClass.compute(
+                method,
+                (ignore, parent) -> {
+                  if (parent == null) {
+                    return new VirtualRootMethod(method);
+                  } else {
+                    VirtualRootMethod override = new VirtualRootMethod(method, parent);
+                    parent.addOverride(override);
+                    return override;
+                  }
+                }));
     return virtualRootMethodsForClass;
   }
 
@@ -147,37 +211,43 @@
           VirtualRootMethod virtualRootMethod =
               virtualRootMethodsForClass.remove(rootCandidate.getMethodSignature());
           acceptVirtualMethod(rootCandidate, virtualRootMethod);
-          if (!rootCandidate.isStructurallyEqualTo(virtualRootMethod.getRoot())) {
+          if (virtualRootMethod.hasParent()
+              || !rootCandidate.isStructurallyEqualTo(virtualRootMethod.getMethod())) {
             return;
           }
-          boolean isMonomorphicVirtualMethod =
-              !clazz.isInterface() && !virtualRootMethod.hasOverrides();
-          if (isMonomorphicVirtualMethod) {
+          if (!virtualRootMethod.hasOverrides()
+              && !virtualRootMethod.hasSiblings()
+              && !virtualRootMethod.isMayDispatchOutsideProgramSet()) {
             monomorphicVirtualRootMethods.add(rootCandidate);
-          } else {
-            ProgramMethod singleNonAbstractMethod = virtualRootMethod.getSingleNonAbstractMethod();
-            if (singleNonAbstractMethod != null
-                && !virtualRootMethod.isInterfaceMethodWithSiblings()) {
+            return;
+          }
+          if (!rootCandidate.getHolder().isInterface()) {
+            VirtualRootMethod singleDispatchTarget = virtualRootMethod.getSingleDispatchTarget();
+            if (singleDispatchTarget != null) {
               virtualRootMethod.forEach(
-                  method -> {
-                    // Interface methods can have siblings and can therefore not be mapped to their
-                    // unique non-abstract implementation, unless the interface method does not have
-                    // any siblings.
-                    virtualRootMethods.put(
-                        method.getReference(), singleNonAbstractMethod.getReference());
-                  });
-              if (!singleNonAbstractMethod.getHolder().isInterface()) {
-                monomorphicVirtualNonRootMethods.add(singleNonAbstractMethod);
-              }
-            } else {
-              virtualRootMethod.forEach(
-                  method ->
-                      virtualRootMethods.put(method.getReference(), rootCandidate.getReference()));
+                  method -> setRootMethod(method, virtualRootMethod, singleDispatchTarget));
+              monomorphicVirtualNonRootMethods.add(singleDispatchTarget.getMethod());
+              return;
             }
           }
+          virtualRootMethod.forEach(
+              method -> setRootMethod(method, virtualRootMethod, virtualRootMethod));
         });
   }
 
+  private void setRootMethod(
+      VirtualRootMethod method, VirtualRootMethod currentRoot, VirtualRootMethod root) {
+    // Since the same method can have multiple roots due to interface methods, we only allow
+    // controlling the virtual root of methods that are rooted at the current root. Otherwise, we
+    // would be setting the virtual root of the same method multiple times, which could lead to
+    // non-determinism in the result (i.e., the `virtualRootMethods` map).
+    if (method.getRoot() == currentRoot) {
+      DexMethod rootReference = root.getMethod().getReference();
+      DexMethod previous = virtualRootMethods.put(method.getMethod().getReference(), rootReference);
+      assert previous == null || previous.isIdenticalTo(rootReference);
+    }
+  }
+
   protected void acceptVirtualMethod(ProgramMethod method, VirtualRootMethod virtualRootMethod) {
     // Intentionally empty.
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index 675bff8..5fcd8db 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -108,10 +108,12 @@
             return;
           }
 
-          // TODO(b/190154391): We should always have an unknown or polymorphic state, but it would
-          //  be better to use a monomorphic state when the interface method is a default method
-          //  with no overrides (CF backend only). In this case, there is no need to add methodState
-          //  to interfaceState.
+          // If the method state is monomorphic, then this is an interface method with no overrides.
+          // In this case, there is no need to add methodState to interfaceState.
+          if (methodState.isMonomorphic()) {
+            return;
+          }
+
           assert methodState.isUnknown() || methodState.asConcrete().isPolymorphic();
           interfaceState.addMethodState(appView, method, methodState);
         });
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
index a6e1da7..70b50c9 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
@@ -68,8 +68,8 @@
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
-    // TODO(b/335584013): Re-enable monomorphic method analysis.
-    ProgramMethodSet monomorphicVirtualMethods = ProgramMethodSet.empty();
+    ProgramMethodSet monomorphicVirtualMethods =
+        computeMonomorphicVirtualRootMethods(executorService);
     ProgramMethodMap<ProgramMethod> singleCallerMethods =
         new SingleCallerScanner(appView, monomorphicVirtualMethods)
             .getSingleCallerMethods(executorService);
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
index 9e78975..2568376 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -57,7 +57,7 @@
     return enableCompletenessCheckForTesting
         && !options.isDesugaredLibraryCompilation()
         && !options.getStartupOptions().isStartupCompletenessCheckForTestingEnabled()
-        && !options.getStartupInstrumentationOptions().isStartupInstrumentationEnabled();
+        && !options.getInstrumentationOptions().isInstrumentationEnabled();
   }
 
   public boolean isNopCheckForTestingEnabled() {
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/InstrumentationOptions.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/InstrumentationOptions.java
new file mode 100644
index 0000000..c1d3fa5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/InstrumentationOptions.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2024, 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.profile.startup.instrumentation;
+
+import static com.android.tools.r8.utils.SystemPropertyUtils.getSystemPropertyForDevelopment;
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Set;
+
+public class InstrumentationOptions {
+
+  /** Set of method references where all calls to the exact method reference should print. */
+  private Set<DexMethod> callSitesToInstrument = Collections.emptySet();
+
+  /**
+   * When enabled, each method will be instrumented to notify the startup InstrumentationServer that
+   * it has been executed.
+   *
+   * <p>This will also inject the startup runtime library (i.e., the InstrumentationServer) into the
+   * app.
+   */
+  private boolean enableExecutedClassesAndMethodsInstrumentation =
+      parseSystemPropertyForDevelopmentOrDefault(
+          "com.android.tools.r8.instrumentation.executedclassesandmethods", false);
+
+  /**
+   * Specifies the synthetic context of the startup runtime library. When this is set, the startup
+   * runtime library will only be injected into the app when the synthetic context is in the
+   * program. This can be used to avoid that the startup runtime library is injected multiple times
+   * in presence of separate compilation.
+   *
+   * <p>Example synthetic context: "app.tivi.home.MainActivity".
+   *
+   * <p>Note that this is only meaningful when one or more instrumentations are enabled.
+   */
+  private String syntheticServerContext =
+      getSystemPropertyForDevelopment(
+          "com.android.tools.r8.instrumentation.syntheticservercontext");
+
+  /**
+   * Specifies the logcat tag that should be used by the InstrumentationServer when logging events.
+   *
+   * <p>When a logcat tag is not specified, the InstrumentationServer will not print events to
+   * logcat. Instead, the startup events must be obtained by requesting the InstrumentationServer to
+   * write the events to a file.
+   */
+  private String tag = getSystemPropertyForDevelopment("com.android.tools.r8.instrumentation.tag");
+
+  public InstrumentationOptions(InternalOptions options) {
+    String callSitesToInstrumentString =
+        getSystemPropertyForDevelopment("com.android.tools.r8.instrumentation.callsites");
+    if (callSitesToInstrumentString != null) {
+      setCallSitesToInstrument(
+          parseCallSitesToInstrument(callSitesToInstrumentString, options.dexItemFactory()));
+    }
+  }
+
+  private static Set<DexMethod> parseCallSitesToInstrument(
+      String smaliStrings, DexItemFactory factory) {
+    ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+    for (String smaliString : Splitter.on(':').split(smaliStrings)) {
+      MethodReference methodReference = MethodReferenceUtils.parseSmaliString(smaliString);
+      if (methodReference == null) {
+        throw new IllegalArgumentException(smaliString);
+      }
+      builder.add(MethodReferenceUtils.toDexMethod(methodReference, factory));
+    }
+    return builder.build();
+  }
+
+  public boolean isInstrumentationEnabled() {
+    return enableExecutedClassesAndMethodsInstrumentation || !callSitesToInstrument.isEmpty();
+  }
+
+  public Set<DexMethod> getCallSitesToInstrument() {
+    return callSitesToInstrument;
+  }
+
+  public void setCallSitesToInstrument(Set<DexMethod> callSitesToInstrument) {
+    this.callSitesToInstrument = callSitesToInstrument;
+  }
+
+  public boolean isExecutedClassesAndMethodsInstrumentationEnabled() {
+    return enableExecutedClassesAndMethodsInstrumentation;
+  }
+
+  public InstrumentationOptions setEnableExecutedClassesAndMethodsInstrumentation() {
+    enableExecutedClassesAndMethodsInstrumentation = true;
+    return this;
+  }
+
+  public boolean hasSyntheticServerContext() {
+    return syntheticServerContext != null;
+  }
+
+  public String getSyntheticServerContext() {
+    return syntheticServerContext;
+  }
+
+  public boolean hasTag() {
+    return tag != null;
+  }
+
+  public String getTag() {
+    return tag;
+  }
+
+  public InstrumentationOptions setTag(String tag) {
+    this.tag = tag;
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
index 521d784..5c43c8b 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
@@ -58,7 +58,7 @@
   private final DexItemFactory dexItemFactory;
   private final InternalOptions options;
   private final StartupInstrumentationReferences references;
-  private final StartupInstrumentationOptions startupInstrumentationOptions;
+  private final InstrumentationOptions instrumentationOptions;
 
   private StartupInstrumentation(AppView<AppInfo> appView) {
     this.appView = appView;
@@ -66,12 +66,12 @@
     this.dexItemFactory = appView.dexItemFactory();
     this.options = appView.options();
     this.references = new StartupInstrumentationReferences(dexItemFactory);
-    this.startupInstrumentationOptions = options.getStartupInstrumentationOptions();
+    this.instrumentationOptions = options.getInstrumentationOptions();
   }
 
   public static void run(AppView<AppInfo> appView, ExecutorService executorService)
       throws ExecutionException {
-    if (appView.options().getStartupInstrumentationOptions().isStartupInstrumentationEnabled()
+    if (appView.options().getInstrumentationOptions().isInstrumentationEnabled()
         && appView.options().isGeneratingDex()) {
       StartupInstrumentation startupInstrumentation = new StartupInstrumentation(appView);
       startupInstrumentation.instrumentAllClasses(executorService);
@@ -97,11 +97,11 @@
     // If the startup options has a synthetic context for the startup instrumentation server, then
     // only inject the runtime library if the synthetic context exists in program to avoid injecting
     // the runtime library multiple times when there is separate compilation.
-    if (startupInstrumentationOptions.hasStartupInstrumentationServerSyntheticContext()) {
+    if (instrumentationOptions.hasSyntheticServerContext()) {
       DexType syntheticContext =
           dexItemFactory.createType(
               DescriptorUtils.javaTypeToDescriptor(
-                  startupInstrumentationOptions.getStartupInstrumentationServerSyntheticContext()));
+                  instrumentationOptions.getSyntheticServerContext()));
       if (asProgramClassOrNull(appView.definitionFor(syntheticContext)) == null) {
         return;
       }
@@ -126,7 +126,7 @@
   private List<DexProgramClass> createStartupRuntimeLibraryClasses() {
     DexProgramClass instrumentationServerImplClass =
         InstrumentationServerImplFactory.createClass(dexItemFactory);
-    if (startupInstrumentationOptions.hasStartupInstrumentationTag()) {
+    if (instrumentationOptions.hasTag()) {
       instrumentationServerImplClass
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("writeToLogcat"))
           .setStaticValue(DexValueBoolean.create(true));
@@ -137,9 +137,7 @@
       instrumentationServerImplClass
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("logcatTag"))
           .setStaticValue(
-              new DexValueString(
-                  dexItemFactory.createString(
-                      startupInstrumentationOptions.getStartupInstrumentationTag())));
+              new DexValueString(dexItemFactory.createString(instrumentationOptions.getTag())));
     }
 
     return ImmutableList.of(
@@ -162,7 +160,8 @@
   }
 
   private boolean ensureClassInitializer(DexProgramClass clazz) {
-    if (clazz.hasClassInitializer()) {
+    if (clazz.hasClassInitializer()
+        || !instrumentationOptions.isExecutedClassesAndMethodsInstrumentationEnabled()) {
       return false;
     }
     ComputedApiLevel computedApiLevel =
@@ -191,7 +190,8 @@
     instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
 
     // Insert invoke to record that the enclosing class is a startup class.
-    if (method.getDefinition().isClassInitializer()) {
+    if (instrumentationOptions.isExecutedClassesAndMethodsInstrumentationEnabled()
+        && method.getDefinition().isClassInitializer()) {
       DexType classToPrint = method.getHolderType();
       Value descriptorValue =
           instructionIterator.insertConstStringInstruction(
@@ -209,15 +209,16 @@
       Value descriptorValue =
           instructionIterator.insertConstStringInstruction(
               appView, code, dexItemFactory.createString(method.getReference().toSmaliString()));
-      instructionIterator.add(
-          InvokeStatic.builder()
-              .setMethod(references.addMethod)
-              .setSingleArgument(descriptorValue)
-              .setPosition(Position.syntheticNone())
-              .build());
+      if (instrumentationOptions.isExecutedClassesAndMethodsInstrumentationEnabled()) {
+        instructionIterator.add(
+            InvokeStatic.builder()
+                .setMethod(references.addMethod)
+                .setSingleArgument(descriptorValue)
+                .setPosition(Position.syntheticNone())
+                .build());
+      }
 
-      Set<DexMethod> callSitesToInstrument =
-          startupInstrumentationOptions.getCallSitesToInstrument();
+      Set<DexMethod> callSitesToInstrument = instrumentationOptions.getCallSitesToInstrument();
       if (!callSitesToInstrument.isEmpty()) {
         do {
           while (instructionIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationOptions.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationOptions.java
deleted file mode 100644
index 4a11409..0000000
--- a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationOptions.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2022, 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.profile.startup.instrumentation;
-
-import static com.android.tools.r8.utils.SystemPropertyUtils.getSystemPropertyForDevelopment;
-import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
-
-import com.android.tools.r8.graph.DexMethod;
-import java.util.Collections;
-import java.util.Set;
-
-public class StartupInstrumentationOptions {
-
-  /** Set of method references where all calls to the exact method reference should print. */
-  private Set<DexMethod> callSitesToInstrument = Collections.emptySet();
-
-  /**
-   * When enabled, each method will be instrumented to notify the startup InstrumentationServer that
-   * it has been executed.
-   *
-   * <p>This will also inject the startup runtime library (i.e., the InstrumentationServer) into the
-   * app.
-   */
-  private boolean enableStartupInstrumentation =
-      parseSystemPropertyForDevelopmentOrDefault(
-          "com.android.tools.r8.startup.instrumentation.instrument", false);
-
-  /**
-   * Specifies the synthetic context of the startup runtime library. When this is set, the startup
-   * runtime library will only be injected into the app when the synthetic context is in the
-   * program. This can be used to avoid that the startup runtime library is injected multiple times
-   * in presence of separate compilation.
-   *
-   * <p>Example synthetic context: "app.tivi.home.MainActivity".
-   *
-   * <p>Note that this is only meaningful when {@link #enableStartupInstrumentation} is set to true.
-   */
-  private String startupInstrumentationServerSyntheticContext =
-      getSystemPropertyForDevelopment(
-          "com.android.tools.r8.startup.instrumentation.instrumentationserversyntheticcontext");
-
-  /**
-   * Specifies the logcat tag that should be used by the InstrumentationServer when logging events.
-   *
-   * <p>When a logcat tag is not specified, the InstrumentationServer will not print events to
-   * logcat. Instead, the startup events must be obtained by requesting the InstrumentationServer to
-   * write the events to a file.
-   */
-  private String startupInstrumentationTag =
-      getSystemPropertyForDevelopment(
-          "com.android.tools.r8.startup.instrumentation.instrumentationtag");
-
-  public Set<DexMethod> getCallSitesToInstrument() {
-    return callSitesToInstrument;
-  }
-
-  public void setCallSitesToInstrument(Set<DexMethod> callSitesToInstrument) {
-    this.callSitesToInstrument = callSitesToInstrument;
-  }
-
-  public boolean hasStartupInstrumentationServerSyntheticContext() {
-    return startupInstrumentationServerSyntheticContext != null;
-  }
-
-  public String getStartupInstrumentationServerSyntheticContext() {
-    return startupInstrumentationServerSyntheticContext;
-  }
-
-  public StartupInstrumentationOptions setStartupInstrumentationServerSyntheticContext(
-      String startupInstrumentationServerSyntheticContext) {
-    this.startupInstrumentationServerSyntheticContext =
-        startupInstrumentationServerSyntheticContext;
-    return this;
-  }
-
-  public boolean hasStartupInstrumentationTag() {
-    return startupInstrumentationTag != null;
-  }
-
-  public String getStartupInstrumentationTag() {
-    return startupInstrumentationTag;
-  }
-
-  public StartupInstrumentationOptions setStartupInstrumentationTag(
-      String startupInstrumentationTag) {
-    this.startupInstrumentationTag = startupInstrumentationTag;
-    return this;
-  }
-
-  public boolean isStartupInstrumentationEnabled() {
-    return enableStartupInstrumentation;
-  }
-
-  public StartupInstrumentationOptions setEnableStartupInstrumentation() {
-    enableStartupInstrumentation = true;
-    return this;
-  }
-}
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 25a690e..6041570 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -268,6 +268,8 @@
   private final Set<EnqueuerConstClassAnalysis> constClassAnalyses = new LinkedHashSet<>();
   private final Set<EnqueuerNewInstanceAnalysis> newInstanceAnalyses = new LinkedHashSet<>();
 
+  private final Map<DexProgramClass, Boolean> rClassLookupCache = new IdentityHashMap<>();
+
   // Don't hold a direct pointer to app info (use appView).
   private AppInfoWithClassHierarchy appInfo;
   private final AppView<AppInfoWithClassHierarchy> appView;
@@ -3804,6 +3806,12 @@
         analysis -> analysis.traceInvokeSuper(reference, resolutionResults, context));
   }
 
+  public boolean isRClass(DexProgramClass dexProgramClass) {
+    return rClassLookupCache.computeIfAbsent(
+        dexProgramClass,
+        clazz -> DescriptorUtils.isRClassDescriptor(clazz.getType().toDescriptorString()));
+  }
+
   // Returns the set of live types.
   public MainDexInfo traceMainDex(ExecutorService executorService, Timing timing)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
index 78abb50..51e854f 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -21,13 +21,11 @@
 
   public static EnqueuerDeferredTracing create(
       AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer, Mode mode) {
-    if (mode.isInitialTreeShaking()) {
+    InternalOptions options = appView.options();
+    if (!options.isShrinking() || !options.enableEnqueuerDeferredTracing) {
       return empty();
     }
-    InternalOptions options = appView.options();
-    if (!options.isOptimizing()
-        || !options.isShrinking()
-        || !options.enableEnqueuerDeferredTracing) {
+    if (!options.isOptimizing() && !options.isOptimizedResourceShrinking()) {
       return empty();
     }
     return new EnqueuerDeferredTracingImpl(appView, enqueuer, mode);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 1eacba7..5291978 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -128,6 +128,11 @@
     // field's holder. Therefore, we unconditionally trace the class initializer in this case.
     // The corresponding IR rewriter will rewrite the field access into an init-class instruction.
     if (accessKind.isStatic()) {
+      if (enqueuer.getMode().isInitialTreeShaking() && field.getHolder() != context.getHolder()) {
+        // TODO(b/338000616): No support for InitClass until we have LIR in the initial round of
+        //  tree shaking.
+        return false;
+      }
       KeepReason reason =
           enqueuer.getGraphReporter().reportClassReferencedFrom(field.getHolder(), context);
       enqueuer.getWorklist().enqueueTraceTypeReferenceAction(field.getHolder(), reason);
@@ -148,7 +153,7 @@
     }
 
     assert enqueuer.getKeepInfo(field).isBottom();
-    assert !enqueuer.getKeepInfo(field).isPinned(options);
+    assert !enqueuer.getKeepInfo(field).isPinned(options) || options.isOptimizedResourceShrinking();
 
     FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
     if (info.hasReflectiveAccess()
@@ -162,6 +167,12 @@
                     || !minimumKeepInfo.isShrinkingAllowed())) {
       return false;
     }
+    if (!options.isOptimizing()) {
+      assert options.isOptimizedResourceShrinking();
+      if (!enqueuer.isRClass(field.getHolder())) {
+        return false;
+      }
+    }
 
     if (info.isWritten()) {
       // If the assigned value may have an override of Object#finalize() then give up.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index bb72018..4644989 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -684,11 +684,7 @@
     // actually equal.
     GraphLens graphLens = appView.graphLens();
     boolean includeContext =
-        intermediate
-            || appView
-                .options()
-                .getStartupInstrumentationOptions()
-                .isStartupInstrumentationEnabled();
+        intermediate || appView.options().getInstrumentationOptions().isInstrumentationEnabled();
     List<T> sortedPotentialMembers =
         ListUtils.sort(
             potentialEquivalence,
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 23a0e46..18de8df 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -63,7 +63,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
@@ -89,7 +88,7 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.profile.art.ArtProfileOptions;
 import com.android.tools.r8.profile.startup.StartupOptions;
-import com.android.tools.r8.profile.startup.instrumentation.StartupInstrumentationOptions;
+import com.android.tools.r8.profile.startup.instrumentation.InstrumentationOptions;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -198,7 +197,6 @@
 
   public ResourceShrinkerConfiguration resourceShrinkerConfiguration =
       ResourceShrinkerConfiguration.DEFAULT_CONFIGURATION;
-  public ResourceAccessAnalysis resourceAccessAnalysis = null;
 
   public boolean checkIfCancelled() {
     if (cancelCompilationChecker == null) {
@@ -263,6 +261,7 @@
     proguardConfiguration = null;
     enableTreeShaking = false;
     enableMinification = false;
+    instrumentationOptions = new InstrumentationOptions(this);
   }
 
   // Constructor for D8, L8, Lint and other non-shrinkers.
@@ -275,6 +274,7 @@
     enableTreeShaking = false;
     enableMinification = false;
     disableGlobalOptimizations();
+    instrumentationOptions = new InstrumentationOptions(this);
   }
 
   // Constructor for R8.
@@ -288,6 +288,7 @@
     itemFactory = proguardConfiguration.getDexItemFactory();
     enableTreeShaking = proguardConfiguration.isShrinking();
     enableMinification = proguardConfiguration.isObfuscating();
+    instrumentationOptions = new InstrumentationOptions(this);
 
     if (!proguardConfiguration.isOptimizing()) {
       // TODO(b/171457102): Avoid the need for this.
@@ -946,8 +947,7 @@
   private final MappingComposeOptions mappingComposeOptions = new MappingComposeOptions();
   private final ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
   private final StartupOptions startupOptions = new StartupOptions();
-  private final StartupInstrumentationOptions startupInstrumentationOptions =
-      new StartupInstrumentationOptions();
+  private final InstrumentationOptions instrumentationOptions;
   public final TestingOptions testing = new TestingOptions();
 
   public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -1043,8 +1043,8 @@
     return startupOptions;
   }
 
-  public StartupInstrumentationOptions getStartupInstrumentationOptions() {
-    return startupInstrumentationOptions;
+  public InstrumentationOptions getInstrumentationOptions() {
+    return instrumentationOptions;
   }
 
   public TestingOptions getTestingOptions() {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
index 377be48..e4872c7 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.ProgramMethod;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -143,6 +144,12 @@
     return backing.compute(key, remappingFunction);
   }
 
+  public T compute(
+      ProgramMethod method,
+      BiFunction<? super DexMethodSignature, ? super T, ? extends T> remappingFunction) {
+    return compute(method.getMethodSignature(), remappingFunction);
+  }
+
   @Override
   public T merge(
       DexMethodSignature key,
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
index 07f10ad..90001a5 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -41,38 +40,31 @@
     CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
     // javac generated an invokedynamic using bootstrap method
     // java.lang.runtime.SwitchBootstraps.typeSwitch.
-    try {
-      assertEquals(
-          1,
-          inspector
-              .clazz(Main.class)
-              .uniqueMethodWithOriginalName("enumSwitch")
-              .streamInstructions()
-              .filter(InstructionSubject::isInvokeDynamic)
-              .map(
-                  instruction ->
-                      instruction
-                          .asCfInstruction()
-                          .getInstruction()
-                          .asInvokeDynamic()
-                          .getCallSite()
-                          .getBootstrapMethod()
-                          .member
-                          .asDexMethod())
-              .filter(
-                  method ->
-                      method
-                          .getHolderType()
-                          .toString()
-                          .contains("java.lang.runtime.SwitchBootstraps"))
-              .filter(method -> method.toString().contains("typeSwitch"))
-              .count());
-    } catch (CompilationError e) {
-      assertEquals("Could not parse code", e.getMessage());
-      assertEquals(
-          "Unsupported bootstrap static argument of type ConstantDynamic",
-          e.getCause().getMessage());
-    }
+    assertEquals(
+        1,
+        inspector
+            .clazz(Main.class)
+            .uniqueMethodWithOriginalName("enumSwitch")
+            .streamInstructions()
+            .filter(InstructionSubject::isInvokeDynamic)
+            .map(
+                instruction ->
+                    instruction
+                        .asCfInstruction()
+                        .getInstruction()
+                        .asInvokeDynamic()
+                        .getCallSite()
+                        .getBootstrapMethod()
+                        .member
+                        .asDexMethod())
+            .filter(
+                method ->
+                    method
+                        .getHolderType()
+                        .toString()
+                        .contains("java.lang.runtime.SwitchBootstraps"))
+            .filter(method -> method.toString().contains("typeSwitch"))
+            .count());
 
     parameters.assumeJvmTestParameters();
     testForJvm(parameters)
@@ -116,6 +108,7 @@
   static final class C implements I {}
 
   static class Main {
+
     static void enumSwitch(I i) {
       switch (i) {
         case E.E1 -> {
diff --git a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
index a330d75..144af9d 100644
--- a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
@@ -104,18 +104,18 @@
         case null -> {
           System.out.println("null");
         }
-        case "y", "Y" -> {
-          System.out.println("y or Y");
-        }
-        case "n", "N" -> {
-          System.out.println("n or N");
-        }
         case String s when s.equalsIgnoreCase("YES") -> {
           System.out.println("yes");
         }
+        case "y", "Y" -> {
+          System.out.println("y or Y");
+        }
         case String s when s.equalsIgnoreCase("NO") -> {
           System.out.println("no");
         }
+        case "n", "N" -> {
+          System.out.println("n or N");
+        }
         case String s -> {
           System.out.println("unknown");
         }
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
index 5ad94ba..f272cde 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -48,24 +47,19 @@
     CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
     // javac generated an invokedynamic using bootstrap method argument of an arrya type (sort 9
     // is org.objectweb.asm.Type.ARRAY).
-    try {
       inspector
           .clazz(Main.class)
           .uniqueMethodWithOriginalName("typeSwitch")
           .streamInstructions()
           .filter(InstructionSubject::isInvokeDynamic)
           .count();
-    } catch (CompilationError e) {
-      assertEquals("Could not parse code", e.getMessage());
-      assertEquals("Type sort is not supported: 9", e.getCause().getMessage());
-    }
     // javac generated an invokedynamic using bootstrap method
     // java.lang.runtime.SwitchBootstraps.typeSwitch.
     assertEquals(
         1,
         inspector
             .clazz(Main.class)
-            .uniqueMethodWithOriginalName("typeSwitchNoArray")
+            .uniqueMethodWithOriginalName("typeSwitch")
             .streamInstructions()
             .filter(InstructionSubject::isInvokeDynamic)
             .map(
@@ -127,17 +121,6 @@
 
   static class Main {
 
-    // No array version only for loading into code inspector.
-    static void typeSwitchNoArray(Object obj) {
-      switch (obj) {
-        case null -> System.out.println("null");
-        case String string -> System.out.println("String");
-        case Color color -> System.out.println("Color: " + color);
-        case Point point -> System.out.println("Record class: " + point);
-        default -> System.out.println("Other");
-      }
-    }
-
     static void typeSwitch(Object obj) {
       switch (obj) {
         case null -> System.out.println("null");
diff --git a/src/test/java/com/android/tools/r8/androidresources/NoOptResourceShrinkingTest.java b/src/test/java/com/android/tools/r8/androidresources/NoOptResourceShrinkingTest.java
new file mode 100644
index 0000000..2248d93
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/NoOptResourceShrinkingTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2024, 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.androidresources;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NoOptResourceShrinkingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean optimized;
+
+  @Parameters(name = "{0}, optimized: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+        BooleanUtils.values());
+  }
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addRClassInitializeWithDefaultValues(R.string.class)
+        .build(temp);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    AndroidTestResource testResources = getTestResources(temp);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(FooBar.class)
+        .applyIf(optimized, R8TestBuilder::enableOptimizedShrinking)
+        .addAndroidResources(testResources)
+        .addKeepMainRule(FooBar.class)
+        .addDontOptimize()
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "bar");
+              resourceTableInspector.assertContainsResourceWithName("string", "foo");
+              resourceTableInspector.assertDoesNotContainResourceWithName(
+                  "string", "unused_string");
+            })
+        .run(parameters.getRuntime(), FooBar.class)
+        .assertSuccess();
+  }
+
+  public static class FooBar {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() == 0) {
+        System.out.println(R.string.bar);
+        System.out.println(R.string.foo);
+      }
+    }
+  }
+
+  public static class R {
+
+    public static class string {
+      public static int bar;
+      public static int foo;
+      public static int unused_string;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
index 74c7102..584426b 100644
--- a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
+++ b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
@@ -107,7 +107,7 @@
 
                 // In optimized mode we track these correctly, so we should not unconditionally keep
                 // all attributes.
-                if (optimized && !debug) {
+                if (optimized) {
                   resourceTableInspector.assertDoesNotContainResourceWithName(
                       "attr", "attr_unused_styleable" + i);
                 } else {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
index c2ab4e2..2cbfd3f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
@@ -38,8 +38,16 @@
         .addInnerClasses(EmptyEnumUnboxingTest.class)
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        // TODO(b/166532373): Unbox enum with no cases.
-        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+        .addEnumUnboxingInspector(
+            inspector -> {
+              if (enumKeepRules.isStudio()) {
+                // TODO(b/166532373): Unbox enum with no cases.
+                inspector.assertNotUnboxed(MyEnum.class);
+              } else {
+                assert enumKeepRules.isNone();
+                inspector.assertUnboxed(MyEnum.class);
+              }
+            })
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index ef9401a..bfb7a05 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AssertionUtils;
 import com.google.common.collect.Sets;
@@ -24,6 +25,7 @@
 import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -68,8 +70,10 @@
                     options -> {
                       options.testing.processingContextsConsumer =
                           id -> assertNull(idsRoundOne.put(id, id));
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressSingleOpenInterface(Reference.classFromClass(Lock.class));
                     }));
-
     compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
 
     Map<String, String> idsRoundTwo = new ConcurrentHashMap<>();
@@ -83,6 +87,9 @@
                             AssertionUtils.assertNotNull(idsRoundOne.get(id));
                             assertNull(idsRoundTwo.put(id, id));
                           };
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressSingleOpenInterface(Reference.classFromClass(Lock.class));
                     }));
 
     // Verify that the result of the two compilations was the same.
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index 4c61934..484a349 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -15,9 +15,11 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.concurrent.locks.Lock;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,6 +61,9 @@
                 builder.addOptionsModification(
                     options -> {
                       options.testing.forceJumboStringProcessing = true;
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressSingleOpenInterface(Reference.classFromClass(Lock.class));
                     }))
         .runDex2Oat(parameters.getRuntime())
         .assertNoVerificationErrors();
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
index cd06774..c6cf0e3 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
@@ -91,9 +91,9 @@
             .addOptionsModification(
                 options ->
                     options
-                        .getStartupInstrumentationOptions()
-                        .setEnableStartupInstrumentation()
-                        .setStartupInstrumentationTag("r8"))
+                        .getInstrumentationOptions()
+                        .setEnableExecutedClassesAndMethodsInstrumentation()
+                        .setTag("r8"))
             .enableCoreLibraryDesugaring(
                 LibraryDesugaringTestConfiguration.builder()
                     .addDesugaredLibraryConfiguration(
diff --git a/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
index a8cf85a..742d9ce 100644
--- a/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
@@ -78,9 +78,9 @@
         .addOptionsModification(
             options ->
                 options
-                    .getStartupInstrumentationOptions()
-                    .setEnableStartupInstrumentation()
-                    .setStartupInstrumentationTag("r8"))
+                    .getInstrumentationOptions()
+                    .setEnableExecutedClassesAndMethodsInstrumentation()
+                    .setTag("r8"))
         .setMinApi(apiLevel)
         .release()
         .compile()
@@ -98,9 +98,9 @@
         .addOptionsModification(
             options ->
                 options
-                    .getStartupInstrumentationOptions()
-                    .setEnableStartupInstrumentation()
-                    .setStartupInstrumentationTag("r8"))
+                    .getInstrumentationOptions()
+                    .setEnableExecutedClassesAndMethodsInstrumentation()
+                    .setTag("r8"))
         .setMinApi(apiLevel)
         .release()
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromBaseMethodWithSingleTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromBaseMethodWithSingleTargetTest.java
new file mode 100644
index 0000000..9a48d47
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromBaseMethodWithSingleTargetTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2024, 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.callsites;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoParameterTypeStrengthening;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Reproduction of b/336791970 where we incorrectly mark A.m() as a monomorphic method meaning that
+ * the argument "B" in the call that resolves to A.m() is not propagated to B.m().
+ */
+@RunWith(Parameterized.class)
+public class PropagationFromBaseMethodWithSingleTargetTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("B", "C");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A a = System.currentTimeMillis() > 0 ? new B() : new A();
+      a.m("B");
+      call(new C());
+    }
+
+    @NeverInline
+    @NoParameterTypeStrengthening
+    static void call(I i) {
+      i.m("C");
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {
+
+    void m(String s);
+  }
+
+  static class A {
+
+    public void m(String s) {
+      System.out.println(s);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B extends A {
+
+    @Override
+    public void m(String s) {
+      System.out.println(s);
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C extends A implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfNonAbstractSiblingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfNonAbstractSiblingTest.java
new file mode 100644
index 0000000..117b090
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfNonAbstractSiblingTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2024, 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.inliner;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalSingleCallerInliningOfNonAbstractSiblingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.SINGLE_CALLER))
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "A");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I i = System.currentTimeMillis() > 0 ? new B() : new C();
+      i.m();
+
+      new A().m();
+    }
+  }
+
+  interface I {
+
+    default void m() {
+      System.out.println("C");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  static class A {
+
+    public void m() {
+      System.out.println("A");
+    }
+  }
+
+  static class B extends A implements I {}
+
+  @NoHorizontalClassMerging
+  static class C implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/PreconditionExtendsRemovedClassIfRuleTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/PreconditionExtendsRemovedClassIfRuleTest.java
new file mode 100644
index 0000000..c709731
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/PreconditionExtendsRemovedClassIfRuleTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2024, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PreconditionExtendsRemovedClassIfRuleTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableUnusedInterfaceRemoval;
+
+  @Parameter(1)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(2)
+  public TestParameters parameters;
+
+  @Parameters(name = "{2}, unused: {0}, vertical: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-if class * implements " + I.class.getTypeName(),
+            "-keep class <1> {",
+            "  void unused();",
+            "}")
+        .applyIf(
+            enableUnusedInterfaceRemoval,
+            R8TestBuilder::addNoUnusedInterfaceRemovalAnnotations,
+            R8TestBuilder::enableNoUnusedInterfaceRemovalAnnotations)
+        .applyIf(
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .setMinApi(parameters)
+        .compile()
+        // TODO(b/337905171): Should keep unused.
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueMethodWithOriginalName("unused"),
+                    isAbsentIf(enableUnusedInterfaceRemoval)));
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(A.class);
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  public interface I {}
+
+  public static class A implements I {
+
+    // Kept by -if.
+    void unused() {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/SubsequentExtendsRemovedClassIfRuleTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/SubsequentExtendsRemovedClassIfRuleTest.java
new file mode 100644
index 0000000..0f99416
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/SubsequentExtendsRemovedClassIfRuleTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2024, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SubsequentExtendsRemovedClassIfRuleTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableUnusedInterfaceRemoval;
+
+  @Parameter(1)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(2)
+  public TestParameters parameters;
+
+  @Parameters(name = "{2}, unused: {0}, vertical: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-if interface *", "-keep class * implements <1> {", "  void unused();", "}")
+        .applyIf(
+            enableUnusedInterfaceRemoval,
+            R8TestBuilder::addNoUnusedInterfaceRemovalAnnotations,
+            R8TestBuilder::enableNoUnusedInterfaceRemovalAnnotations)
+        .applyIf(
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .allowUnusedProguardConfigurationRules(enableUnusedInterfaceRemoval)
+        .setMinApi(parameters)
+        .compile()
+        // TODO(b/337905171): Should keep unused.
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueMethodWithOriginalName("unused"),
+                    isAbsentIf(enableUnusedInterfaceRemoval)));
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(A.class);
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  public interface I {}
+
+  public static class A implements I {
+
+    // Kept by -if.
+    void unused() {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index f51ab4b..41c6730 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils;
 import com.android.tools.r8.profile.art.HumanReadableArtProfileParser;
 import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
-import com.android.tools.r8.profile.startup.instrumentation.StartupInstrumentationOptions;
+import com.android.tools.r8.profile.startup.instrumentation.InstrumentationOptions;
 import com.android.tools.r8.startup.StartupClassBuilder;
 import com.android.tools.r8.startup.StartupMethodBuilder;
 import com.android.tools.r8.startup.StartupProfileBuilder;
@@ -100,11 +100,12 @@
     testBuilder
         .addOptionsModification(
             options -> {
-              StartupInstrumentationOptions startupInstrumentationOptions =
-                  options.getStartupInstrumentationOptions().setEnableStartupInstrumentation();
+              InstrumentationOptions startupInstrumentationOptions =
+                  options
+                      .getInstrumentationOptions()
+                      .setEnableExecutedClassesAndMethodsInstrumentation();
               if (logcat) {
-                startupInstrumentationOptions.setStartupInstrumentationTag(
-                    startupInstrumentationTag);
+                startupInstrumentationOptions.setTag(startupInstrumentationTag);
               }
             })
         .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 263aa76..6cebc5e 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -17,7 +17,7 @@
 
 import utils
 
-R8_DEV_BRANCH = '8.5'
+R8_DEV_BRANCH = '8.6'
 R8_VERSION_FILE = os.path.join('src', 'main', 'java', 'com', 'android', 'tools',
                                'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/startup/instrument.py b/tools/startup/instrument.py
index 9752670..7e2870f 100755
--- a/tools/startup/instrument.py
+++ b/tools/startup/instrument.py
@@ -30,6 +30,14 @@
     result.add_argument('--discard',
                         action='append',
                         help='Name of dex files to discard')
+    result.add_argument('--print-boxing-unboxing-callsites',
+                        action='store_true',
+                        default=False,
+                        help='Print caller->callee edges for primitive boxing')
+    result.add_argument('--print-executed-classes-and-methods',
+                        action='store_true',
+                        default=False,
+                        help='Print the classes and methods that are executed')
     result.add_argument('--out',
                         help='Destination of resulting apk',
                         required=True)
@@ -52,14 +60,40 @@
                         tmp_dir):
     d8_cmd = [
         'java', '-cp', utils.R8_JAR,
-        '-Dcom.android.tools.r8.startup.instrumentation.instrument=1',
-        '-Dcom.android.tools.r8.startup.instrumentation.instrumentationtag=R8'
+        '-Dcom.android.tools.r8.instrumentation.tag=R8'
     ]
+    if options.print_boxing_unboxing_callsites:
+        callsites = ':'.join([
+                # Boxing
+                "Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;",
+                "Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte;",
+                "Ljava/lang/Character;->valueOf(C)Ljava/lang/Character;",
+                "Ljava/lang/Double;->valueOf(D)Ljava/lang/Double;",
+                "Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;",
+                "Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;",
+                "Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;",
+                "Ljava/lang/Short;->valueOf(S)Ljava/lang/Short;",
+                # Unboxing
+                "Ljava/lang/Boolean;->booleanValue()Z",
+                "Ljava/lang/Byte;->byteValue()B",
+                "Ljava/lang/Character;->charValue()C",
+                "Ljava/lang/Double;->doubleValue()D",
+                "Ljava/lang/Float;->floatValue()F",
+                "Ljava/lang/Integer;->intValue()I",
+                "Ljava/lang/Long;->longValue()J",
+                "Ljava/lang/Short;->shortValue()S"
+            ])
+        d8_cmd.append(
+            '-Dcom.android.tools.r8.instrumentation.callsites='
+                + callsites)
+    if options.print_executed_classes_and_methods:
+        d8_cmd.append(
+            '-Dcom.android.tools.r8.instrumentation.executedclassesandmethods=1')
     if not include_instrumentation_server:
         # We avoid injecting the InstrumentationServer by specifying it should only
         # be added if foo.bar.Baz is in the program.
         d8_cmd.append(
-            '-Dcom.android.tools.r8.startup.instrumentation.instrumentationserversyntheticcontext=foo.bar.Baz'
+            '-Dcom.android.tools.r8.instrumentation.syntheticservercontext=foo.bar.Baz'
         )
     d8_cmd.extend([
         'com.android.tools.r8.D8', '--min-api',