Support for logging calls to logcat

Change-Id: Id50c631949fb0ae9d459891a654bb9cc240eb19b
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 192178b..521d784 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.profile.startup.instrumentation;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.utils.PredicateUtils.not;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -17,14 +18,16 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -44,6 +47,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -67,7 +71,8 @@
 
   public static void run(AppView<AppInfo> appView, ExecutorService executorService)
       throws ExecutionException {
-    if (appView.options().getStartupInstrumentationOptions().isStartupInstrumentationEnabled()) {
+    if (appView.options().getStartupInstrumentationOptions().isStartupInstrumentationEnabled()
+        && appView.options().isGeneratingDex()) {
       StartupInstrumentation startupInstrumentation = new StartupInstrumentation(appView);
       startupInstrumentation.instrumentAllClasses(executorService);
       startupInstrumentation.injectStartupRuntimeLibrary(executorService);
@@ -126,6 +131,10 @@
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("writeToLogcat"))
           .setStaticValue(DexValueBoolean.create(true));
       instrumentationServerImplClass
+          .lookupUniqueStaticFieldWithName(
+              dexItemFactory.createString("writeToLogcatIncludeDuplicates"))
+          .setStaticValue(DexValueBoolean.create(true));
+      instrumentationServerImplClass
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("logcatTag"))
           .setStaticValue(
               new DexValueString(
@@ -137,11 +146,10 @@
         InstrumentationServerFactory.createClass(dexItemFactory), instrumentationServerImplClass);
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private void instrumentClass(DexProgramClass clazz) {
     // Do not instrument the instrumentation server if it is already in the app.
-    if (clazz.getType() == references.instrumentationServerType
-        || clazz.getType() == references.instrumentationServerImplType) {
+    if (clazz.getType().isIdenticalTo(references.instrumentationServerType)
+        || clazz.getType().isIdenticalTo(references.instrumentationServerImplType)) {
       return;
     }
 
@@ -178,7 +186,8 @@
     // finalizing the code.
     MutableMethodConversionOptions conversionOptions = MethodConversionOptions.forD8(appView);
     IRCode code = method.buildIR(appView, conversionOptions);
-    InstructionListIterator instructionIterator = code.entryBlock().listIterator(code);
+    BasicBlockIterator blocks = code.listIterator();
+    InstructionListIterator instructionIterator = blocks.next().listIterator(code);
     instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
 
     // Insert invoke to record that the enclosing class is a startup class.
@@ -197,18 +206,59 @@
 
     // Insert invoke to record the execution of the current method.
     if (!skipMethodLogging) {
-      DexReference referenceToPrint = method.getReference();
       Value descriptorValue =
           instructionIterator.insertConstStringInstruction(
-              appView, code, dexItemFactory.createString(referenceToPrint.toSmaliString()));
+              appView, code, dexItemFactory.createString(method.getReference().toSmaliString()));
       instructionIterator.add(
           InvokeStatic.builder()
               .setMethod(references.addMethod)
               .setSingleArgument(descriptorValue)
               .setPosition(Position.syntheticNone())
               .build());
+
+      Set<DexMethod> callSitesToInstrument =
+          startupInstrumentationOptions.getCallSitesToInstrument();
+      if (!callSitesToInstrument.isEmpty()) {
+        do {
+          while (instructionIterator.hasNext()) {
+            Instruction instruction = instructionIterator.next();
+            if (instruction.isInvokeMethod()) {
+              DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+              if (callSitesToInstrument.contains(invokedMethod)) {
+                instructionIterator.previous();
+                ConstString calleeString =
+                    ConstString.builder()
+                        .setFreshOutValue(
+                            code,
+                            dexItemFactory.stringType.toTypeElement(appView, definitelyNotNull()))
+                        .setPosition(instruction.getPosition())
+                        .setValue(dexItemFactory.createString(invokedMethod.toSmaliString()))
+                        .build();
+                instructionIterator =
+                    instructionIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+                        code,
+                        blocks,
+                        ImmutableList.of(
+                            calleeString,
+                            InvokeStatic.builder()
+                                .setMethod(references.addCall)
+                                .setArguments(descriptorValue, calleeString.outValue())
+                                .setPosition(instruction.getPosition())
+                                .build()),
+                        options);
+                Instruction next = instructionIterator.next();
+                assert next == instruction;
+              }
+            }
+          }
+          if (blocks.hasNext()) {
+            instructionIterator = blocks.next().listIterator(code);
+          }
+        } while (instructionIterator.hasNext());
+      }
     }
 
+    code.removeRedundantBlocks();
     converter.deadCodeRemover.run(code, Timing.empty());
 
     DexCode instrumentedCode =
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
index ad94406..4a11409 100644
--- 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
@@ -7,8 +7,15 @@
 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.
@@ -45,6 +52,14 @@
       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;
   }
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationReferences.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationReferences.java
index 78d55f0..7b180a8 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationReferences.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationReferences.java
@@ -12,6 +12,7 @@
 
   final DexType instrumentationServerType;
   final DexType instrumentationServerImplType;
+  final DexMethod addCall;
   final DexMethod addMethod;
 
   StartupInstrumentationReferences(DexItemFactory dexItemFactory) {
@@ -19,6 +20,12 @@
         dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServer;");
     instrumentationServerImplType =
         dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;");
+    addCall =
+        dexItemFactory.createMethod(
+            instrumentationServerImplType,
+            dexItemFactory.createProto(
+                dexItemFactory.voidType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "addCall");
     addMethod =
         dexItemFactory.createMethod(
             instrumentationServerImplType,
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
index e9d64ab..f6d1bf4 100644
--- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
+++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
@@ -126,6 +126,16 @@
               dexItemFactory.createField(
                   dexItemFactory.createType(
                       "Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                  dexItemFactory.createType("Z"),
+                  dexItemFactory.createString("writeToLogcatIncludeDuplicates")))
+          .setAccessFlags(FieldAccessFlags.fromCfAccessFlags(10))
+          .setApiLevel(ComputedApiLevel.unknown())
+          .build(),
+      DexEncodedField.syntheticBuilder()
+          .setField(
+              dexItemFactory.createField(
+                  dexItemFactory.createType(
+                      "Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
                   dexItemFactory.createType("Ljava/lang/String;"),
                   dexItemFactory.createString("logcatTag")))
           .setAccessFlags(FieldAccessFlags.fromCfAccessFlags(10))
@@ -162,7 +172,23 @@
                       dexItemFactory.createType(
                           "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
                   dexItemFactory.createString("getInstance")))
-          .setCode(method -> createCfCode4_getInstance(dexItemFactory, method))
+          .setCode(method -> createCfCode5_getInstance(dexItemFactory, method))
+          .build(),
+      DexEncodedMethod.syntheticBuilder()
+          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(9, false))
+          .setApiLevelForCode(ComputedApiLevel.unknown())
+          .setApiLevelForDefinition(ComputedApiLevel.unknown())
+          .setClassFileVersion(CfVersion.V1_8)
+          .setMethod(
+              dexItemFactory.createMethod(
+                  dexItemFactory.createType(
+                      "Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                  dexItemFactory.createProto(
+                      dexItemFactory.createType("V"),
+                      dexItemFactory.createType("Ljava/lang/String;"),
+                      dexItemFactory.createType("Ljava/lang/String;")),
+                  dexItemFactory.createString("addCall")))
+          .setCode(method -> createCfCode2_addCall(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
           .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(9, false))
@@ -177,7 +203,7 @@
                       dexItemFactory.createType("V"),
                       dexItemFactory.createType("Ljava/lang/String;")),
                   dexItemFactory.createString("addMethod")))
-          .setCode(method -> createCfCode3_addMethod(dexItemFactory, method))
+          .setCode(method -> createCfCode4_addMethod(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
           .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(2, false))
@@ -192,10 +218,10 @@
                       dexItemFactory.createType("V"),
                       dexItemFactory.createType("Ljava/lang/String;")),
                   dexItemFactory.createString("addLine")))
-          .setCode(method -> createCfCode2_addLine(dexItemFactory, method))
+          .setCode(method -> createCfCode3_addLine(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
-          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(2, false))
+          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(10, false))
           .setApiLevelForCode(ComputedApiLevel.unknown())
           .setApiLevelForDefinition(ComputedApiLevel.unknown())
           .setClassFileVersion(CfVersion.V1_8)
@@ -207,7 +233,7 @@
                       dexItemFactory.createType("V"),
                       dexItemFactory.createType("Ljava/lang/String;")),
                   dexItemFactory.createString("writeToLogcat")))
-          .setCode(method -> createCfCode6_writeToLogcat(dexItemFactory, method))
+          .setCode(method -> createCfCode7_writeToLogcat(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
           .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(8, true))
@@ -239,7 +265,7 @@
                   dexItemFactory.createProto(
                       dexItemFactory.createType("V"), dexItemFactory.createType("Ljava/io/File;")),
                   dexItemFactory.createString("writeToFile")))
-          .setCode(method -> createCfCode5_writeToFile(dexItemFactory, method))
+          .setCode(method -> createCfCode6_writeToFile(dexItemFactory, method))
           .build()
     };
   }
@@ -314,7 +340,71 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode2_addLine(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode2_addCall(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfNew(factory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.stringType),
+                    factory.createString("append")),
+                false),
+            new CfConstString(factory.createString(" -> ")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.stringType),
+                    factory.createString("append")),
+                false),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.stringType),
+                    factory.createString("append")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringType),
+                    factory.createString("toString")),
+                false),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("writeToLogcat")),
+                false),
+            label1,
+            new CfReturnVoid(),
+            label2),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode createCfCode3_addLine(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
     CfLabel label2 = new CfLabel();
@@ -327,12 +417,48 @@
     CfLabel label9 = new CfLabel();
     CfLabel label10 = new CfLabel();
     CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    CfLabel label13 = new CfLabel();
+    CfLabel label14 = new CfLabel();
     return new CfCode(
         method.holder,
         2,
         4,
         ImmutableList.of(
             label0,
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                    factory.booleanType,
+                    factory.createString("writeToLogcat"))),
+            new CfIf(IfType.EQ, ValueType.INT, label3),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                    factory.booleanType,
+                    factory.createString("writeToLogcatIncludeDuplicates"))),
+            new CfIf(IfType.EQ, ValueType.INT, label3),
+            label1,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("writeToLogcat")),
+                false),
+            label2,
+            new CfReturnVoid(),
+            label3,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType(
+                              "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
+                      FrameType.initializedNonNullReference(factory.stringType)
+                    })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceFieldRead(
                 factory.createField(
@@ -342,7 +468,7 @@
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfStore(ValueType.OBJECT, 2),
             new CfMonitor(MonitorType.ENTER),
-            label1,
+            label4,
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceFieldRead(
                 factory.createField(
@@ -357,13 +483,13 @@
                     factory.createProto(factory.booleanType, factory.objectType),
                     factory.createString("add")),
                 false),
-            new CfIf(IfType.NE, ValueType.INT, label4),
-            label2,
+            new CfIf(IfType.NE, ValueType.INT, label7),
+            label5,
             new CfLoad(ValueType.OBJECT, 2),
             new CfMonitor(MonitorType.EXIT),
-            label3,
+            label6,
             new CfReturnVoid(),
-            label4,
+            label7,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
@@ -376,9 +502,9 @@
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfMonitor(MonitorType.EXIT),
-            label5,
-            new CfGoto(label8),
-            label6,
+            label8,
+            new CfGoto(label11),
+            label9,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
@@ -394,10 +520,10 @@
             new CfStore(ValueType.OBJECT, 3),
             new CfLoad(ValueType.OBJECT, 2),
             new CfMonitor(MonitorType.EXIT),
-            label7,
+            label10,
             new CfLoad(ValueType.OBJECT, 3),
             new CfThrow(),
-            label8,
+            label11,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
@@ -412,18 +538,17 @@
                     factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
                     factory.booleanType,
                     factory.createString("writeToLogcat"))),
-            new CfIf(IfType.EQ, ValueType.INT, label10),
-            label9,
-            new CfLoad(ValueType.OBJECT, 0),
+            new CfIf(IfType.EQ, ValueType.INT, label13),
+            label12,
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
-                183,
+                184,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
                     factory.createProto(factory.voidType, factory.stringType),
                     factory.createString("writeToLogcat")),
                 false),
-            label10,
+            label13,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
@@ -434,18 +559,21 @@
                       FrameType.initializedNonNullReference(factory.stringType)
                     })),
             new CfReturnVoid(),
-            label11),
+            label14),
         ImmutableList.of(
             new CfTryCatch(
-                label1, label3, ImmutableList.of(factory.throwableType), ImmutableList.of(label6)),
+                label4, label6, ImmutableList.of(factory.throwableType), ImmutableList.of(label9)),
             new CfTryCatch(
-                label4, label5, ImmutableList.of(factory.throwableType), ImmutableList.of(label6)),
+                label7, label8, ImmutableList.of(factory.throwableType), ImmutableList.of(label9)),
             new CfTryCatch(
-                label6, label7, ImmutableList.of(factory.throwableType), ImmutableList.of(label6))),
+                label9,
+                label10,
+                ImmutableList.of(factory.throwableType),
+                ImmutableList.of(label9))),
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode3_addMethod(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode4_addMethod(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
     CfLabel label2 = new CfLabel();
@@ -479,7 +607,7 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode4_getInstance(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode5_getInstance(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     return new CfCode(
         method.holder,
@@ -497,7 +625,7 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode5_writeToFile(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode6_writeToFile(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
     CfLabel label2 = new CfLabel();
@@ -724,14 +852,14 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode6_writeToLogcat(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode7_writeToLogcat(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
     CfLabel label2 = new CfLabel();
     return new CfCode(
         method.holder,
         2,
-        2,
+        1,
         ImmutableList.of(
             label0,
             new CfStaticFieldRead(
@@ -739,7 +867,7 @@
                     factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
                     factory.stringType,
                     factory.createString("logcatTag"))),
-            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 184,
                 factory.createMethod(
diff --git a/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java b/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
index 1f19f3c..11e586a 100644
--- a/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
+++ b/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
@@ -15,6 +15,7 @@
 
   // May be set to true by the instrumentation.
   private static boolean writeToLogcat;
+  private static boolean writeToLogcatIncludeDuplicates;
   private static String logcatTag;
 
   private final LinkedHashSet<String> lines = new LinkedHashSet<>();
@@ -25,11 +26,19 @@
     return InstrumentationServerImpl.INSTANCE;
   }
 
+  public static void addCall(String callerDescriptor, String calleeDescriptor) {
+    writeToLogcat(callerDescriptor + " -> " + calleeDescriptor);
+  }
+
   public static void addMethod(String descriptor) {
     getInstance().addLine(descriptor);
   }
 
   private void addLine(String line) {
+    if (writeToLogcat && writeToLogcatIncludeDuplicates) {
+      writeToLogcat(line);
+      return;
+    }
     synchronized (lines) {
       if (!lines.add(line)) {
         return;
@@ -54,7 +63,7 @@
     }
   }
 
-  private void writeToLogcat(String line) {
+  private static void writeToLogcat(String line) {
     android.util.Log.i(logcatTag, line);
   }
 }