Dump contexts of boxing/unboxing operations at runtime

Change-Id: Ia3cf42f2edc9e7e70ca7dc07d283a9e004c086a2
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
index 69bc2d2..c1d3fa5 100644
--- 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
@@ -7,7 +7,13 @@
 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;
 
@@ -23,9 +29,9 @@
    * <p>This will also inject the startup runtime library (i.e., the InstrumentationServer) into the
    * app.
    */
-  private boolean enableStartupInstrumentation =
+  private boolean enableExecutedClassesAndMethodsInstrumentation =
       parseSystemPropertyForDevelopmentOrDefault(
-          "com.android.tools.r8.startup.instrumentation.instrument", false);
+          "com.android.tools.r8.instrumentation.executedclassesandmethods", false);
 
   /**
    * Specifies the synthetic context of the startup runtime library. When this is set, the startup
@@ -35,11 +41,11 @@
    *
    * <p>Example synthetic context: "app.tivi.home.MainActivity".
    *
-   * <p>Note that this is only meaningful when {@link #enableStartupInstrumentation} is set to true.
+   * <p>Note that this is only meaningful when one or more instrumentations are enabled.
    */
-  private String startupInstrumentationServerSyntheticContext =
+  private String syntheticServerContext =
       getSystemPropertyForDevelopment(
-          "com.android.tools.r8.startup.instrumentation.instrumentationserversyntheticcontext");
+          "com.android.tools.r8.instrumentation.syntheticservercontext");
 
   /**
    * Specifies the logcat tag that should be used by the InstrumentationServer when logging events.
@@ -48,9 +54,33 @@
    * 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");
+  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;
@@ -60,40 +90,33 @@
     this.callSitesToInstrument = callSitesToInstrument;
   }
 
-  public boolean hasStartupInstrumentationServerSyntheticContext() {
-    return startupInstrumentationServerSyntheticContext != null;
+  public boolean isExecutedClassesAndMethodsInstrumentationEnabled() {
+    return enableExecutedClassesAndMethodsInstrumentation;
   }
 
-  public String getStartupInstrumentationServerSyntheticContext() {
-    return startupInstrumentationServerSyntheticContext;
-  }
-
-  public InstrumentationOptions setStartupInstrumentationServerSyntheticContext(
-      String startupInstrumentationServerSyntheticContext) {
-    this.startupInstrumentationServerSyntheticContext =
-        startupInstrumentationServerSyntheticContext;
+  public InstrumentationOptions setEnableExecutedClassesAndMethodsInstrumentation() {
+    enableExecutedClassesAndMethodsInstrumentation = true;
     return this;
   }
 
-  public boolean hasStartupInstrumentationTag() {
-    return startupInstrumentationTag != null;
+  public boolean hasSyntheticServerContext() {
+    return syntheticServerContext != null;
   }
 
-  public String getStartupInstrumentationTag() {
-    return startupInstrumentationTag;
+  public String getSyntheticServerContext() {
+    return syntheticServerContext;
   }
 
-  public InstrumentationOptions setStartupInstrumentationTag(String startupInstrumentationTag) {
-    this.startupInstrumentationTag = startupInstrumentationTag;
-    return this;
+  public boolean hasTag() {
+    return tag != null;
   }
 
-  public boolean isStartupInstrumentationEnabled() {
-    return enableStartupInstrumentation;
+  public String getTag() {
+    return tag;
   }
 
-  public InstrumentationOptions setEnableStartupInstrumentation() {
-    enableStartupInstrumentation = true;
+  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 96c4ca4..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 InstrumentationOptions 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/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 15701b3..f2492dc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -263,6 +263,7 @@
     proguardConfiguration = null;
     enableTreeShaking = false;
     enableMinification = false;
+    instrumentationOptions = new InstrumentationOptions(this);
   }
 
   // Constructor for D8, L8, Lint and other non-shrinkers.
@@ -275,6 +276,7 @@
     enableTreeShaking = false;
     enableMinification = false;
     disableGlobalOptimizations();
+    instrumentationOptions = new InstrumentationOptions(this);
   }
 
   // Constructor for R8.
@@ -288,6 +290,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,7 +949,7 @@
   private final MappingComposeOptions mappingComposeOptions = new MappingComposeOptions();
   private final ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
   private final StartupOptions startupOptions = new StartupOptions();
-  private final InstrumentationOptions startupInstrumentationOptions = new InstrumentationOptions();
+  private final InstrumentationOptions instrumentationOptions;
   public final TestingOptions testing = new TestingOptions();
 
   public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -1042,8 +1045,8 @@
     return startupOptions;
   }
 
-  public InstrumentationOptions getStartupInstrumentationOptions() {
-    return startupInstrumentationOptions;
+  public InstrumentationOptions getInstrumentationOptions() {
+    return instrumentationOptions;
   }
 
   public TestingOptions getTestingOptions() {
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/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index 42d09b1..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
@@ -101,10 +101,11 @@
         .addOptionsModification(
             options -> {
               InstrumentationOptions startupInstrumentationOptions =
-                  options.getStartupInstrumentationOptions().setEnableStartupInstrumentation();
+                  options
+                      .getInstrumentationOptions()
+                      .setEnableExecutedClassesAndMethodsInstrumentation();
               if (logcat) {
-                startupInstrumentationOptions.setStartupInstrumentationTag(
-                    startupInstrumentationTag);
+                startupInstrumentationOptions.setTag(startupInstrumentationTag);
               }
             })
         .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
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',