Merge "Revert "Fix bug making us not inline when a simple class without clinit had static fields with nothing setting them.""
diff --git a/build.gradle b/build.gradle
index 33d967f..5606a9e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -567,6 +567,7 @@
     from repackageSources.outputs.files
     from repackageDeps.outputs.files
     configureRelocations(it)
+    exclude "META-INF/*.kotlin_module"
 }
 
 task r8WithoutDeps(type: ShadowJar) {
@@ -596,6 +597,7 @@
     } else {
         from sourceSets.main.output
     }
+    exclude "META-INF/*.kotlin_module"
 }
 
 task R8NoManifestNoDeps(type: ShadowJar) {
@@ -619,6 +621,7 @@
     } else {
         from sourceSets.main.output
     }
+    exclude "META-INF/*.kotlin_module"
 }
 
 task D8(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 7b17ec8..c5f0a40 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutionException;
@@ -88,13 +90,14 @@
         "Usage: disasm [options] <input-files>\n"
             + " where <input-files> are dex files\n"
             + " and options are:\n"
-            + "  --all                   # Include all information in disassembly.\n"
-            + "  --smali                 # Disassemble using smali syntax.\n"
-            + "  --ir                    # Print IR before and after optimization.\n"
-            + "  --pg-map <file>         # Proguard map <file> for mapping names.\n"
-            + "  --output                # Specify a file or directory to write to.\n"
-            + "  --version               # Print the version of r8.\n"
-            + "  --help                  # Print this message.";
+            + "  --all                       # Include all information in disassembly.\n"
+            + "  --smali                     # Disassemble using smali syntax.\n"
+            + "  --ir                        # Print IR before and after optimization.\n"
+            + "  --pg-map <file>             # Proguard map <file> for mapping names.\n"
+            + "  --pg-map-charset <charset>  # Charset for Proguard map file.\n"
+            + "  --output                    # Specify a file or directory to write to.\n"
+            + "  --version                   # Print the version of r8.\n"
+            + "  --help                      # Print this message.";
 
     private final boolean allInfo;
     private final boolean useSmali;
@@ -127,6 +130,18 @@
           builder.setUseIr(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
+        } else if (arg.equals("--pg-map-charset")) {
+          String charset = args[++i];
+          try {
+            Charset.forName(charset);
+          } catch (UnsupportedCharsetException e) {
+            builder.getReporter().error(
+                new StringDiagnostic(
+                    "Unsupported charset: " + charset + "." + System.lineSeparator()
+                        + "Supported charsets are: "
+                        + String.join(", ", Charset.availableCharsets().keySet()),
+                CommandLineOrigin.INSTANCE));
+          }
         } else if (arg.equals("--output")) {
           String outputPath = args[++i];
           builder.setOutputPath(Paths.get(outputPath));
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index fd85c25..c9f2032 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -70,6 +70,12 @@
     return extractMarker(app);
   }
 
+  public static Collection<Marker> extractMarkerFromClassProgramData(byte[] data)
+      throws IOException, ExecutionException {
+    AndroidApp app = AndroidApp.builder().addClassProgramData(data, Origin.unknown()).build();
+    return extractMarker(app);
+  }
+
   private static void addDexResources(AndroidApp.Builder appBuilder, Path file)
       throws IOException, ResourceException {
     if (FileUtils.isVDexFile(file)) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 30b57da..c59b56b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -194,6 +194,7 @@
         new CfApplicationWriter(
                 application,
                 options,
+                marker,
                 deadCode,
                 graphLense,
                 namingLens,
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 83d115d..b5814d2 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.5.1-dev";
+  public static final String LABEL = "1.5.2-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e02869d..f8fb8e1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -197,7 +197,15 @@
             graphLense.getOriginalMethodSignature(encodedMethod.method),
             null,
             appInfo);
-    IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options, origin);
+    IRBuilder builder =
+        new IRBuilder(
+            encodedMethod,
+            appInfo,
+            source,
+            options,
+            origin,
+            new ValueNumberGenerator(),
+            graphLense);
     return builder.build(encodedMethod);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 44294db..32d3c37 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -812,7 +812,6 @@
     public static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
     public static boolean UNKNOWN_RETURNS_CONSTANT = false;
     public static int UNKNOWN_RETURNED_CONSTANT = -1;
-    public static boolean NOT_PUBLICZED = false;
     public static boolean DOES_NOT_USE_IDNETIFIER_NAME_STRING = false;
     public static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
     public static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
@@ -932,7 +931,6 @@
         DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NORMALLY;
     private boolean returnsConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT;
     private long returnedConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT;
-    private boolean publicized = DefaultOptimizationInfoImpl.NOT_PUBLICZED;
     private InlinePreference inlining = InlinePreference.Default;
     private boolean useIdentifierNameString =
         DefaultOptimizationInfoImpl.DOES_NOT_USE_IDNETIFIER_NAME_STRING;
@@ -978,7 +976,6 @@
       neverReturnsNormally = template.neverReturnsNormally;
       returnsConstant = template.returnsConstant;
       returnedConstant = template.returnedConstant;
-      publicized = template.publicized;
       inlining = template.inlining;
       useIdentifierNameString = template.useIdentifierNameString;
       checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
@@ -1165,16 +1162,6 @@
     }
 
     @Override
-    public void markPublicized() {
-      publicized = true;
-    }
-
-    @Override
-    public void unsetPublicized() {
-      publicized = false;
-    }
-
-    @Override
     public void markUseIdentifierNameString() {
       useIdentifierNameString = true;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 3f06bbc..11d9730 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -74,6 +74,7 @@
 
         private int argumentIndex = -1;
         private boolean isAlwaysNull = false;
+        private DexType type = null;
 
         public Builder setArgumentIndex(int argumentIndex) {
           this.argumentIndex = argumentIndex;
@@ -85,18 +86,26 @@
           return this;
         }
 
+        public Builder setType(DexType type) {
+          this.type = type;
+          return this;
+        }
+
         public RemovedArgumentInfo build() {
           assert argumentIndex >= 0;
-          return new RemovedArgumentInfo(argumentIndex, isAlwaysNull);
+          assert type != null;
+          return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type);
         }
       }
 
       private final int argumentIndex;
       private final boolean isAlwaysNull;
+      private final DexType type;
 
-      private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull) {
+      private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) {
         this.argumentIndex = argumentIndex;
         this.isAlwaysNull = isAlwaysNull;
+        this.type = type;
       }
 
       public static Builder builder() {
@@ -107,6 +116,10 @@
         return argumentIndex;
       }
 
+      public DexType getType() {
+        return type;
+      }
+
       public boolean isAlwaysNull() {
         return isAlwaysNull;
       }
@@ -117,7 +130,7 @@
 
       public RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
         return this.argumentIndex != argumentIndex
-            ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull)
+            ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
             : this;
       }
     }
@@ -694,8 +707,8 @@
    * <p>Subclasses can override the lookup methods.
    *
    * <p>For method mapping where invocation type can change just override {@link
-   * #mapInvocationType(DexMethod, DexMethod, DexMethod, Type)} if the default name mapping applies,
-   * and only invocation type might need to change.
+   * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
+   * invocation type might need to change.
    */
   public static class NestedGraphLense extends GraphLense {
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 55ece50..3170b63 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
@@ -110,6 +111,22 @@
     reader.accept(
         new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
         parsingOptions);
+
+    // Read marker.
+    if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
+        && reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
+      try {
+        Object maybeMarker =
+            reader.readConst(
+                CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
+                new char[reader.getMaxStringLength()]);
+        if (maybeMarker instanceof String) {
+          application.getFactory().createString((String) maybeMarker);
+        }
+      } catch (IllegalArgumentException e) {
+        // Ignore if the type of the constant is not something readConst() allows.
+      }
+    }
   }
 
   private static int cleanAccessFlags(int access) {
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
index aefd2a8..8751816 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
@@ -42,8 +42,4 @@
   void unsetForceInline();
 
   void markNeverInline();
-
-  void markPublicized();
-
-  void unsetPublicized();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 881b255..4738403 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -38,9 +38,9 @@
 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.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CanonicalPositions;
@@ -50,14 +50,14 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 
 public class DexSourceCode implements SourceCode {
 
   private final DexCode code;
-  private final MethodAccessFlags accessFlags;
-  private final DexProto proto;
+  private final DexEncodedMethod method;
 
   // Mapping from instruction offset to instruction index in the DexCode instruction array.
   private final Map<Integer, Integer> offsetToInstructionIndex = new HashMap<>();
@@ -73,8 +73,6 @@
   private Position currentPosition = null;
   private final CanonicalPositions canonicalPositions;
 
-  private final List<TypeLatticeElement> argumentTypes;
-
   private List<DexDebugEntry> debugEntries = null;
   // In case of inlining the position of the invoke in the caller.
   private final DexMethod originalMethod;
@@ -86,11 +84,8 @@
       Position callerPosition,
       AppInfo appInfo) {
     this.code = code;
-    this.proto = method.method.proto;
-    this.accessFlags = method.accessFlags;
+    this.method = method;
     this.originalMethod = originalMethod;
-
-    argumentTypes = computeArgumentTypes(appInfo);
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
       debugEntries = info.computeEntries(originalMethod);
@@ -149,16 +144,46 @@
     if (code.incomingRegisterSize == 0) {
       return;
     }
+
+    RemovedArgumentsInfo removedArgumentsInfo = builder.prototypeChanges.getRemovedArgumentsInfo();
+    ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator();
+    RemovedArgumentInfo nextRemovedArgument =
+        removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+
     // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
     // block.
+    int argumentIndex = 0;
+
     int register = code.registerSize - code.incomingRegisterSize;
-    if (!accessFlags.isStatic()) {
+    if (!method.isStatic()) {
       builder.addThisArgument(register);
+      ++argumentIndex;
       ++register;
     }
-    for (TypeLatticeElement typeLattice : argumentTypes) {
-      builder.addNonThisArgument(register, typeLattice);
-      register += typeLattice.requiredRegisters();
+
+    int numberOfArguments =
+        method.method.proto.parameters.values.length
+            + removedArgumentsInfo.numberOfRemovedArguments()
+            + (method.isStatic() ? 0 : 1);
+
+    for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) {
+      TypeLatticeElement type;
+      if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) {
+        type =
+            TypeLatticeElement.fromDexType(
+                nextRemovedArgument.getType(), Nullability.maybeNull(), builder.getAppInfo());
+        builder.addConstantOrUnusedArgument(register);
+        nextRemovedArgument =
+            removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+      } else {
+        type =
+            TypeLatticeElement.fromDexType(
+                method.method.proto.parameters.values[usedArgumentIndex++],
+                Nullability.maybeNull(),
+                builder.getAppInfo());
+        builder.addNonThisArgument(register, type);
+      }
+      register += type.requiredRegisters();
     }
     builder.flushArgumentInstructions();
   }
@@ -310,14 +335,6 @@
         arrayFilledDataPayloadResolver.getData(payloadOffset));
   }
 
-  private List<TypeLatticeElement> computeArgumentTypes(AppInfo appInfo) {
-    List<TypeLatticeElement> types = new ArrayList<>(proto.parameters.size());
-    for (DexType type : proto.parameters.values) {
-      types.add(TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appInfo));
-    }
-    return types;
-  }
-
   private boolean isInvoke(Instruction dex) {
     return dex instanceof InvokeDirect
         || dex instanceof InvokeDirectRange
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 045ba3e..37b3d1b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -396,7 +396,7 @@
   private DexEncodedMethod context;
   private final AppInfo appInfo;
   private final Origin origin;
-  private RewrittenPrototypeDescription prototypeChanges;
+  final RewrittenPrototypeDescription prototypeChanges;
   private ListIterator<RemovedArgumentInfo> removedArgumentsIterator;
   private int argumentCount = 0;
 
@@ -894,15 +894,8 @@
       DebugLocalInfo local = getOutgoingLocal(register);
       Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
       addInstruction(new Argument(value));
-    } else if (removedArgumentInfo.isAlwaysNull()) {
-      if (pendingArgumentInstructions == null) {
-        pendingArgumentInstructions = new ArrayList<>();
-      }
-      DebugLocalInfo local = getOutgoingLocal(register);
-      Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
-      pendingArgumentInstructions.add(new ConstNumber(value, 0));
     } else {
-      assert removedArgumentInfo.isNeverUsed();
+      handleConstantOrUnusedArgument(register, removedArgumentInfo);
     }
   }
 
@@ -918,6 +911,25 @@
     }
   }
 
+  public void addConstantOrUnusedArgument(int register) {
+    handleConstantOrUnusedArgument(register, getRemovedArgumentInfo());
+  }
+
+  private void handleConstantOrUnusedArgument(
+      int register, RemovedArgumentInfo removedArgumentInfo) {
+    assert removedArgumentInfo != null;
+    if (removedArgumentInfo.isAlwaysNull()) {
+      if (pendingArgumentInstructions == null) {
+        pendingArgumentInstructions = new ArrayList<>();
+      }
+      DebugLocalInfo local = getOutgoingLocal(register);
+      Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
+      pendingArgumentInstructions.add(new ConstNumber(value, 0));
+    } else {
+      assert removedArgumentInfo.isNeverUsed();
+    }
+  }
+
   public void flushArgumentInstructions() {
     if (pendingArgumentInstructions != null) {
       pendingArgumentInstructions.forEach(this::addInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index ed1994b..5d2daed 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1011,7 +1011,8 @@
     codeRewriter.rewriteSwitch(code);
     codeRewriter.processMethodsNeverReturningNormally(code);
     codeRewriter.simplifyIf(code);
-    codeRewriter.redundantConstNumberRemoval(code);
+    // TODO(b/123284765) This produces a runtime-crash in Q. Activate again when fixed.
+    // codeRewriter.redundantConstNumberRemoval(code);
     new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
 
     if (options.testing.invertConditionals) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index e1e534e..568e021 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -65,7 +65,7 @@
         twrUtilityClass, twrCloseResourceProto, factory.twrCloseResourceMethodName);
   }
 
-  // Rewrites calls to $closeResource() method. Can me invoked concurrently.
+  // Rewrites calls to $closeResource() method. Can be invoked concurrently.
   public void rewriteMethodCode(IRCode code) {
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c1364f6..e41593f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -298,7 +298,11 @@
           removedArgumentsInfo = new ArrayList<>();
         }
         removedArgumentsInfo.add(
-            RemovedArgumentInfo.builder().setArgumentIndex(i + offset).setIsAlwaysNull().build());
+            RemovedArgumentInfo.builder()
+                .setArgumentIndex(i + offset)
+                .setIsAlwaysNull()
+                .setType(type)
+                .build());
       }
     }
     return removedArgumentsInfo != null
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 061907f..aae8429 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -208,8 +208,8 @@
       return null;
     }
     assert method.getCode().getOwner() == method;
-    int argumentCount =
-        method.method.proto.parameters.size() + (method.accessFlags.isStatic() ? 0 : 1);
+    int offset = method.accessFlags.isStatic() ? 0 : 1;
+    int argumentCount = method.method.proto.parameters.size() + offset;
     // TODO(65810338): Implement for virtual methods as well.
     if (method.accessFlags.isPrivate()
         || method.accessFlags.isStatic()
@@ -224,9 +224,13 @@
       BitSet used = collector.getUsedArguments();
       if (used.cardinality() < argumentCount) {
         List<RemovedArgumentInfo> unused = new ArrayList<>();
-        for (int i = 0; i < argumentCount; i++) {
-          if (!used.get(i)) {
-            unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
+        for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
+          if (!used.get(argumentIndex)) {
+            unused.add(
+                RemovedArgumentInfo.builder()
+                    .setArgumentIndex(argumentIndex)
+                    .setType(method.method.proto.parameters.values[argumentIndex - offset])
+                    .build());
           }
         }
         return new RemovedArgumentsInfo(unused);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 14459fe..05120db 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.Code;
@@ -61,10 +62,15 @@
   private static final boolean RUN_VERIFIER = false;
   private static final boolean PRINT_CF = false;
 
+  // First item inserted into the constant pool is the marker string which generates an UTF8 to
+  // pool index #1 and a String entry to #2, referencing #1.
+  public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
+
   private final DexApplication application;
   private final GraphLense graphLense;
   private final NamingLens namingLens;
   private final InternalOptions options;
+  private final String markerString;
 
   public final ProguardMapSupplier proguardMapSupplier;
   public final String deadCode;
@@ -73,6 +79,7 @@
   public CfApplicationWriter(
       DexApplication application,
       InternalOptions options,
+      Marker marker,
       String deadCode,
       GraphLense graphLense,
       NamingLens namingLens,
@@ -82,6 +89,7 @@
     this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.options = options;
+    this.markerString = marker == null ? null : marker.toString();
     this.proguardMapSupplier = proguardMapSupplier;
     this.deadCode = deadCode;
     this.proguardSeedsData = proguardSeedsData;
@@ -117,6 +125,10 @@
 
   private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
     ClassWriter writer = new ClassWriter(0);
+    if (markerString != null) {
+      int index = writer.newConst(markerString);
+      assert index == MARKER_STRING_CONSTANT_POOL_INDEX;
+    }
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
     int version = getClassFileVersion(clazz);
     int access = clazz.accessFlags.getAsCfAccessFlags();
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index cdf96c2..706620f 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -171,6 +171,11 @@
         return null;
       }
       DexType holderType = classValue.getConstInstruction().asConstClass().getValue();
+      if (holderType.isArrayType()) {
+        // None of the fields or methods of an array type will be renamed, since they are all
+        // declared in the library. Hence there is no need to handle this case.
+        return null;
+      }
       DexClass holder = appInfo.definitionFor(holderType);
       if (holder == null) {
         return null;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 9934ab2..5547315 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -318,7 +318,8 @@
       throw new ParseException("Identifier expected");
     }
     nextCodePoint();
-    while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
+    while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())
+        || IdentifierUtils.isQuestionMark(peekCodePoint())) {
       nextCodePoint();
     }
     if (isInit) {
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 72fc987..d3f336f 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -133,7 +133,6 @@
       // Although the current method became public, it surely has the single virtual target.
       encodedMethod.method.setSingleVirtualMethodCache(
           encodedMethod.method.getHolder(), encodedMethod);
-      encodedMethod.getMutableOptimizationInfo().markPublicized();
       return true;
     }
 
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 9118fda..31a76ab 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -89,10 +90,11 @@
         Assert.assertNotNull(mainClassDescriptor);
         for (String descriptor : descriptors) {
           // classes are either lambda classes used by the main class, companion classes of the main
-          // interface or the main class/interface
+          // interface, the main class/interface, or for JDK9, desugaring of try-with-resources.
           Assert.assertTrue(descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
               || descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
               || descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
+              || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
               || descriptor.equals(mainClassDescriptor));
         }
         String classDescriptor =
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 5a12e62..6a47607 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -15,8 +16,9 @@
 public class ExtractMarkerTest {
 
   @Test
-  public void extractMarkerTest() throws CompilationFailedException {
+  public void extractMarkerTestDex() throws CompilationFailedException {
     String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+    boolean[] testExecuted = {false};
     D8.run(
         D8Command.builder()
             .addProgramFiles(Paths.get(classFile))
@@ -42,8 +44,46 @@
                     assertEquals(
                         CompilationMode.DEBUG.toString().toLowerCase(),
                         marker.getCompilationMode());
+                    testExecuted[0] = true;
                   }
                 })
             .build());
+    assertTrue(testExecuted[0]);
+  }
+
+  @Test
+  public void extractMarkerTestCf() throws CompilationFailedException {
+    String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+    boolean[] testExecuted = {false};
+    R8.run(
+        R8Command.builder()
+            .addProgramFiles(Paths.get(classFile))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setProgramConsumer(
+                new ClassFileConsumer.ForwardingConsumer(null) {
+                  @Override
+                  public void accept(
+                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                    Marker marker;
+                    try {
+                      Collection<Marker> markers =
+                          ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+                      assertEquals(1, markers.size());
+                      marker = markers.iterator().next();
+                    } catch (Exception e) {
+                      throw new RuntimeException(e);
+                    }
+                    assertEquals(Tool.R8, marker.getTool());
+                    assertEquals(Version.LABEL, marker.getVersion());
+                    assertEquals(
+                        CompilationMode.DEBUG.toString().toLowerCase(),
+                        marker.getCompilationMode());
+                    testExecuted[0] = true;
+                  }
+                })
+            .build());
+    assertTrue(testExecuted[0]);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index fb61f45..1c0287e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -50,6 +51,7 @@
 import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
 import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ObjectReferenceCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
@@ -153,6 +155,12 @@
   }
 
   protected final void runDebugTest(
+      DebugTestConfig config, Class<?> debuggeeClass, JUnit3Wrapper.Command... commands)
+      throws Throwable {
+    runInternal(config, debuggeeClass.getTypeName(), Arrays.asList(commands));
+  }
+
+  protected final void runDebugTest(
       DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
       throws Throwable {
     runInternal(config, debuggeeClass, Arrays.asList(commands));
@@ -324,6 +332,10 @@
     return new JUnit3Wrapper.Command.RunCommand();
   }
 
+  protected final JUnit3Wrapper.Command breakpoint(MethodReference method) {
+    return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName());
+  }
+
   protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
     return breakpoint(className, methodName, null);
   }
@@ -477,6 +489,10 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkLine(int line) {
+    return inspect(t -> t.checkLine(null, line));
+  }
+
   protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
     return inspect(t -> t.checkLine(sourceFile, line));
   }
@@ -532,6 +548,16 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkFieldOnThis(
+      String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(
+        t -> {
+          Value value = t.getFieldOnThis(fieldName, fieldSignature);
+          Assert.assertEquals(
+              "Incorrect value for field 'this." + fieldName + "'", expectedValue, value);
+        });
+  }
+
   protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
     return t -> inspector.accept(t.debuggeeState);
   }
@@ -1298,6 +1324,7 @@
 
         @Override
         public void checkLine(String sourceFile, int line) {
+          sourceFile = sourceFile != null ? sourceFile : getSourceFile();
           if (!Objects.equals(sourceFile, getSourceFile()) || line != getLineNumber()) {
             String locationString = convertCurrentLocationToString();
             Assert.fail(
@@ -1501,7 +1528,15 @@
 
         // The class is available, lookup and read the field.
         long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
-        return getField(getMirror(), classId, fieldId);
+        return internalStaticField(getMirror(), classId, fieldId);
+      }
+
+      public Value getFieldOnThis(String fieldName, String fieldSignature) {
+        long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+        long classId = getMirror().getReferenceType(thisObjectId);
+        // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+        long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+        return internalInstanceField(getMirror(), thisObjectId, fieldId);
       }
 
       private long findField(VmMirror mirror, long classId, String fieldName,
@@ -1547,10 +1582,10 @@
         return matchingFieldIds.getLong(0);
       }
 
-      private Value getField(VmMirror mirror, long classId, long fieldId) {
-
-        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
-            ReferenceTypeCommandSet.GetValuesCommand);
+      private static Value internalStaticField(VmMirror mirror, long classId, long fieldId) {
+        CommandPacket commandPacket =
+            new CommandPacket(
+                ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.GetValuesCommand);
         commandPacket.setNextValueAsReferenceTypeID(classId);
         commandPacket.setNextValueAsInt(1);
         commandPacket.setNextValueAsFieldID(fieldId);
@@ -1565,6 +1600,23 @@
       }
     }
 
+    private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
+      CommandPacket commandPacket =
+          new CommandPacket(
+              ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.GetValuesCommand);
+      commandPacket.setNextValueAsObjectID(objectId);
+      commandPacket.setNextValueAsInt(1);
+      commandPacket.setNextValueAsFieldID(fieldId);
+      ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+      assert replyPacket.getErrorCode() == Error.NONE;
+
+      int fieldsCount = replyPacket.getNextValueAsInt();
+      assert fieldsCount == 1;
+      Value result = replyPacket.getNextValueAsValue();
+      Assert.assertTrue(replyPacket.isAllDataRead());
+      return result;
+    }
+
     public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
         String localName) {
       return getVariablesAt(mirror, location).stream()
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
new file mode 100644
index 0000000..9991b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class LambdaOuterContextTest {
+
+  public interface Converter {
+    String convert(int value);
+  }
+
+  public int outer;
+
+  public LambdaOuterContextTest(int outer) {
+    this.outer = outer;
+  }
+
+  public void foo(Converter converter) {
+    System.out.println(converter.convert(outer));
+  }
+
+  public void bar(int arg) {
+    foo(value -> {
+      // Ensure lambda uses parts of the outer context, otherwise javac will optimize it out.
+      return Integer.toString(outer + value + arg);
+    });
+  }
+
+  public static void main(String[] args) {
+    new LambdaOuterContextTest(args.length + 42).bar(args.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
new file mode 100644
index 0000000..8af2a6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LambdaOuterContextTestRunner extends DebugTestBase {
+
+  public static final Class<?> CLASS = LambdaOuterContextTest.class;
+  public static final String EXPECTED = StringUtils.lines("84");
+
+  @Test
+  public void testJvm() throws Throwable {
+    JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
+    jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(jvmTestBuilder.debugConfig());
+  }
+
+  @Test
+  @Ignore("b/123068053")
+  public void testD8() throws Throwable {
+    D8TestCompileResult compileResult =
+        testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  @Test
+  public void testR8Cf() throws Throwable {
+    R8TestCompileResult compileResult =
+        testForR8(Backend.CF)
+            .addProgramClassesAndInnerClasses(CLASS)
+            .debug()
+            .noMinification()
+            .noTreeShaking()
+            .compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  private void runDebugger(DebugTestConfig config) throws Throwable {
+    runDebugTest(
+        config,
+        CLASS,
+        breakpoint(methodFromMethod(CLASS.getMethod("foo", Converter.class))),
+        run(),
+        checkLine(19),
+        checkLocals("this", "converter"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        stepInto(INTELLIJ_FILTER),
+        checkLine(25),
+        checkLocals("this", "value", "arg"),
+        checkNoLocal("outer"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 1b413ba..f9e6c34 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -13,7 +13,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-// TODO(shertz) test local variables
 @RunWith(Parameterized.class)
 public class LambdaTest extends DebugTestBase {
 
@@ -47,8 +46,11 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 12),
+        checkLocals("i"),
+        checkNoLocal("j"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 16),
+        checkLocals("i", "j"),
         run());
   }
 
@@ -63,8 +65,10 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 32),
+        checkLocals("i", "a", "b"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 37),
+        checkLocals("a", "b"),
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
index fc382ee..6b281cb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.Streams;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -41,6 +42,7 @@
   }
 
   @Test
+  @Ignore("b/123284765")
   public void test() throws Exception {
     String expectedOutput =
         StringUtils.lines(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
new file mode 100644
index 0000000..71c5fbe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningFromCurrentClassTest extends TestBase {
+
+  @Ignore("b/123327416")
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines(
+            "In A.<clinit>()",
+            "In B.<clinit>()",
+            "In A.inlineable1()",
+            "In B.inlineable2()",
+            "In C.<clinit>()",
+            "In C.notInlineable()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(InliningFromCurrentClassTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    ClassSubject classB = inspector.clazz(B.class);
+    assertThat(classB, isPresent());
+
+    ClassSubject classC = inspector.clazz(C.class);
+    assertThat(classC, isPresent());
+
+    MethodSubject inlineable1Method = classA.uniqueMethodWithName("inlineable1");
+    assertThat(inlineable1Method, not(isPresent()));
+
+    MethodSubject inlineable2Method = classB.uniqueMethodWithName("inlineable2");
+    assertThat(inlineable2Method, not(isPresent()));
+
+    MethodSubject notInlineableMethod = classC.uniqueMethodWithName("notInlineable");
+    assertThat(notInlineableMethod, isPresent());
+
+    MethodSubject testMethod = classB.uniqueMethodWithName("test");
+    assertThat(testMethod, isPresent());
+    assertThat(testMethod, not(invokesMethod(inlineable1Method)));
+    assertThat(testMethod, not(invokesMethod(inlineable2Method)));
+    assertThat(testMethod, invokesMethod(notInlineableMethod));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      B.test();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    static {
+      System.out.println("In A.<clinit>()");
+    }
+
+    static void inlineable1() {
+      System.out.println("In A.inlineable1()");
+    }
+  }
+
+  @NeverMerge
+  static class B extends A {
+
+    static {
+      System.out.println("In B.<clinit>()");
+    }
+
+    @NeverInline
+    static void test() {
+      A.inlineable1();
+      B.inlineable2();
+      C.notInlineable();
+    }
+
+    static void inlineable2() {
+      System.out.println("In B.inlineable2()");
+    }
+  }
+
+  static class C extends B {
+
+    static {
+      System.out.println("In C.<clinit>()");
+    }
+
+    static void notInlineable() {
+      System.out.println("In C.notInlineable()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
new file mode 100644
index 0000000..9691149
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningWithClassInitializerTest extends TestBase {
+
+  @Ignore("b/123327413")
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines("In A.<clinit>()", "In B.inlineable()", "In B.other()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(InliningWithClassInitializerTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    ClassSubject classB = inspector.clazz(B.class);
+    assertThat(classB, isPresent());
+
+    MethodSubject inlineableMethod = classB.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+
+    MethodSubject otherMethod = classB.uniqueMethodWithName("other");
+    assertThat(otherMethod, isPresent());
+
+    MethodSubject mainMethod = inspector.clazz(TestClass.class).mainMethod();
+    assertThat(mainMethod, invokesMethod(otherMethod));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Should be inlined since the call to `B.other()` will ensure that the static initalizer in
+      // A will continue to be executed even after inlining.
+      B.inlineable();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    static {
+      System.out.println("In A.<clinit>()");
+    }
+  }
+
+  static class B extends A {
+
+    static void inlineable() {
+      System.out.println("In B.inlineable()");
+      other();
+    }
+
+    @NeverInline
+    static void other() {
+      System.out.println("In B.other()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 8a8f137..7cb49d3 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -29,7 +29,13 @@
     String mapping =
         "com.c.c.b -> com.c.c.b:\n" +
             "    1287:1287:int ?(int,int) -> ?";
+    ClassNameMapper.mapperFromString(mapping);
 
+    // From some other proguard generated map
+    mapping = "com.moat.analytics.mobile.cha.b -> com.moat.analytics.mobile.cha.b:\n"
+        + "    com.moat.analytics.mobile.cha.MoatAdEventType[] ? -> ?\n"
+        + "    java.util.HashMap ? -> ?\n"
+        + "    java.util.HashSet ?? -> ??\n";
     ClassNameMapper.mapperFromString(mapping);
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
new file mode 100644
index 0000000..7d5381a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.function.Predicate;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class CodeMatchers {
+
+  public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
+    if (!targetSubject.isPresent()) {
+      throw new IllegalArgumentException();
+    }
+    DexMethod target = targetSubject.getMethod().method;
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        if (!subject.isPresent()) {
+          return false;
+        }
+        if (!subject.getMethod().hasCode()) {
+          return false;
+        }
+        return subject.streamInstructions().anyMatch(isInvokeWithTarget(target));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(" does not invoke `" + target.toSourceString() + "`");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
+  public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
+    return instruction -> instruction.isInvokeStatic() && instruction.getMethod() == target;
+  }
+}
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index 6013e6f..fa46485 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -8,7 +8,6 @@
 import optparse
 import os
 import shutil
-import subprocess
 import sys
 import utils
 
@@ -32,6 +31,9 @@
   parser.add_option('--adb-options',
                     help='additional adb options when running adb',
                     default=None)
+  parser.add_option('--quiet',
+                    help='disable verbose logging',
+                    default=False)
   (options, args) = parser.parse_args()
   if len(args) != 1:
     parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -41,44 +43,44 @@
 def findKeystore():
   return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
 
-def repack(processed_out, original_apk, temp):
+def repack(processed_out, original_apk, temp, quiet):
   processed_apk = os.path.join(temp, 'processed.apk')
   shutil.copyfile(original_apk, processed_apk)
   if not processed_out:
-    print 'Using original APK as is'
+    utils.Print('Using original APK as is', quiet=quiet)
     return processed_apk
-  print 'Repacking APK with dex files from', processed_apk
-  with utils.ChangedWorkingDirectory(temp):
+  utils.Print(
+      'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
+  with utils.ChangedWorkingDirectory(temp, quiet=quiet):
     cmd = ['zip', '-d', 'processed.apk', '*.dex']
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    utils.RunCmd(cmd, quiet=quiet)
   if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
     cmd = ['unzip', processed_out, '-d', temp]
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    if quiet:
+      cmd.insert(1, '-q')
+    utils.RunCmd(cmd, quiet=quiet)
     processed_out = temp
-  with utils.ChangedWorkingDirectory(processed_out):
+  with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
     dex = glob.glob('*.dex')
     cmd = ['zip', '-u', '-9', processed_apk] + dex
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    utils.RunCmd(cmd, quiet=quiet)
   return processed_apk
 
-def sign(unsigned_apk, keystore, temp):
+def sign(unsigned_apk, keystore, temp, quiet):
   signed_apk = os.path.join(temp, 'unaligned.apk')
-  apk_utils.sign(unsigned_apk, signed_apk, keystore)
+  apk_utils.sign(unsigned_apk, signed_apk, keystore, quiet=quiet)
   return signed_apk
 
-def align(signed_apk, temp):
-  print 'Aligning'
+def align(signed_apk, temp, quiet):
+  utils.Print('Aligning', quiet=quiet)
   aligned_apk = os.path.join(temp, 'aligned.apk')
   cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
-  print ' '.join(cmd)
-  subprocess.check_call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
   return signed_apk
 
 def masseur(
-    apk, dex=None, out=None, adb_options=None, keystore=None, install=False):
+    apk, dex=None, out=None, adb_options=None, keystore=None, install=False,
+    quiet=False):
   if not out:
     out = os.path.basename(apk)
   if not keystore:
@@ -86,23 +88,23 @@
   with utils.TempDir() as temp:
     processed_apk = None
     if dex:
-      processed_apk = repack(dex, apk, temp)
+      processed_apk = repack(dex, apk, temp, quiet)
     else:
-      print 'Signing original APK without modifying dex files'
+      utils.Print(
+          'Signing original APK without modifying dex files', quiet=quiet)
       processed_apk = os.path.join(temp, 'processed.apk')
       shutil.copyfile(apk, processed_apk)
-    signed_apk = sign(processed_apk, keystore, temp)
-    aligned_apk = align(signed_apk, temp)
-    print 'Writing result to', out
+    signed_apk = sign(processed_apk, keystore, temp, quiet=quiet)
+    aligned_apk = align(signed_apk, temp, quiet=quiet)
+    utils.Print('Writing result to {}'.format(out), quiet=quiet)
     shutil.copyfile(aligned_apk, out)
-    adb_cmd = ['adb']
-    if adb_options:
-      adb_cmd.extend(
-          [option for option in adb_options.split(' ') if option])
     if install:
+      adb_cmd = ['adb']
+      if adb_options:
+        adb_cmd.extend(
+            [option for option in adb_options.split(' ') if option])
       adb_cmd.extend(['install', '-t', '-r', '-d', out]);
-      utils.PrintCmd(adb_cmd)
-      subprocess.check_call(adb_cmd)
+      utils.RunCmd(adb_cmd, quiet=quiet)
 
 def main():
   (options, apk) = parse_options()
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index aaae1e4..5a6aa94 100644
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -7,11 +7,10 @@
 import subprocess
 import utils
 
-def sign(unsigned_apk, signed_apk, keystore):
-  print 'Signing (ignore the warnings)'
+def sign(unsigned_apk, signed_apk, keystore, quiet=False):
+  utils.Print('Signing (ignore the warnings)', quiet=quiet)
   cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
-  utils.PrintCmd(cmd)
-  subprocess.call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
   cmd = [
     'jarsigner',
     '-sigalg', 'SHA1withRSA',
@@ -22,8 +21,7 @@
     unsigned_apk,
     'androiddebugkey'
   ]
-  utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
 
 def sign_with_apksigner(build_tools_dir, unsigned_apk, signed_apk, keystore, password):
   cmd = [
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 9c2c8c8..10fb29f 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -30,23 +30,15 @@
             'Unexpected line with \'dependencies {\'')
         is_inside_dependencies = True
       if is_inside_dependencies:
-        if '/r8.jar' in stripped:
-          if minified:
-            # Skip line to avoid dependency on r8.jar
-            continue
-          added_r8_dependency = True
-        elif '/r8lib.jar' in stripped:
-          if not minified:
-            # Skip line to avoid dependency on r8lib.jar
-            continue
-          added_r8_dependency = True
+        if '/r8.jar' in stripped or '/r8lib.jar' in stripped:
+          # Skip line to avoid dependency on r8.jar
+          continue
         elif 'com.android.tools.build:gradle:' in stripped:
           gradle_version = stripped[stripped.rindex(':')+1:-1]
-          if not added_r8_dependency:
-            indent = ''.ljust(line.index('classpath'))
-            jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
-            f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
-            added_r8_dependency = True
+          indent = ''.ljust(line.index('classpath'))
+          jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
+          f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
+          added_r8_dependency = True
         elif stripped == '}':
           is_inside_dependencies = False
       f.write(line)
@@ -147,8 +139,9 @@
   # one of the predefined locations.
   assert False
 
-def Move(src, dst):
-  print('Moving `{}` to `{}`'.format(src, dst))
+def Move(src, dst, quiet=False):
+  if not quiet:
+    print('Moving `{}` to `{}`'.format(src, dst))
   dst_parent = os.path.dirname(dst)
   if not os.path.isdir(dst_parent):
     os.makedirs(dst_parent)
@@ -158,15 +151,15 @@
     os.remove(dst)
   os.rename(src, dst)
 
-def MoveDir(src, dst):
+def MoveDir(src, dst, quiet=False):
   assert os.path.isdir(src)
-  Move(src, dst)
+  Move(src, dst, quiet=quiet)
 
-def MoveFile(src, dst):
+def MoveFile(src, dst, quiet=False):
   assert os.path.isfile(src)
-  Move(src, dst)
+  Move(src, dst, quiet=quiet)
 
-def MoveProfileReportTo(dest_dir, build_stdout):
+def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
   html_file = None
   profile_message = 'See the profiling report at: '
   for line in build_stdout:
@@ -181,11 +174,12 @@
 
   assert os.path.isfile(html_file), 'Expected to find HTML file at {}'.format(
       html_file)
-  MoveFile(html_file, os.path.join(dest_dir, 'index.html'))
+  MoveFile(html_file, os.path.join(dest_dir, 'index.html'), quiet=quiet)
 
   html_dir = os.path.dirname(html_file)
   for dir_name in ['css', 'js']:
-    MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name))
+    MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name),
+        quiet=quiet)
 
 def ParseProfileReport(profile_dir):
   html_file = os.path.join(profile_dir, 'index.html')
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 128b6e3..e084bad 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -42,6 +42,8 @@
       'app_id': 'de.danoeh.antennapod',
       'git_repo': 'https://github.com/christofferqa/AntennaPod.git',
       'flavor': 'play',
+      'min_sdk': 14,
+      'compile_sdk': 26
   },
   'apps-android-wikipedia': {
       'app_id': 'org.wikipedia',
@@ -49,6 +51,10 @@
       'flavor': 'prod',
       'signed-apk-name': 'app-prod-universal-release.apk'
   },
+  'chanu': {
+    'app_id': 'com.chanapps.four.activity',
+    'git_repo': 'https://github.com/mkj-gram/chanu.git',
+  },
   'friendlyeats-android': {
       'app_id': 'com.google.firebase.example.fireeats',
       'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git'
@@ -69,6 +75,14 @@
       'app_id': 'org.schabi.newpipe',
       'git_repo': 'https://github.com/christofferqa/NewPipe',
   },
+  'Signal-Android': {
+    'app_id': 'org.thoughtcrime.securesms',
+    'app_module': '',
+    'flavor': 'play',
+    'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
+    'releaseTarget': 'assemblePlayRelease',
+    'signed-apk-name': 'Signal-play-release-4.32.7.apk',
+  },
   'Simple-Calendar': {
       'app_id': 'com.simplemobiletools.calendar.pro',
       'git_repo': 'https://github.com/christofferqa/Simple-Calendar',
@@ -89,18 +103,6 @@
       # TODO(123047413): Fails with R8.
       'skip': True,
   },
-  'Signal-Android': {
-    'app_id': 'org.thoughtcrime.securesms',
-    'app_module': '',
-    'flavor': 'play',
-    'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
-    'releaseTarget': 'assemblePlayRelease',
-    'signed-apk-name': 'Signal-play-release-4.32.7.apk',
-  },
-  'chanu': {
-    'app_id': 'com.chanapps.four.activity',
-    'git_repo': 'https://github.com/mkj-gram/chanu.git',
-  },
   # This does not build yet.
   'muzei': {
       'git_repo': 'https://github.com/sgjesse/muzei.git',
@@ -128,7 +130,7 @@
       dex_size += z.getinfo(filename).file_size
   return dex_size
 
-def IsBuiltWithR8(apk, temp_dir):
+def IsBuiltWithR8(apk, temp_dir, options):
   r8_jar = os.path.join(temp_dir, 'r8.jar')
 
   # Use the copy of r8.jar if it is there.
@@ -138,7 +140,7 @@
     script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
     cmd = ['python', script, apk]
 
-  utils.PrintCmd(cmd)
+  utils.PrintCmd(cmd, quiet=options.quiet)
   return '~~R8' in subprocess.check_output(cmd).strip()
 
 def IsMinifiedR8(shrinker):
@@ -157,9 +159,13 @@
 def GitCheckout(file):
   return subprocess.check_output(['git', 'checkout', file]).strip()
 
-def InstallApkOnEmulator(apk_dest):
-  subprocess.check_call(
-      ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+def InstallApkOnEmulator(apk_dest, options):
+  cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest]
+  if options.quiet:
+    with open(os.devnull, 'w') as devnull:
+      subprocess.check_call(cmd, stdout=devnull)
+  else:
+    subprocess.check_call(cmd)
 
 def PercentageDiffAsString(before, after):
   if after < before:
@@ -167,7 +173,7 @@
   else:
     return '+' + str(round((after - before) / before * 100)) + '%'
 
-def UninstallApkOnEmulator(app, config):
+def UninstallApkOnEmulator(app, config, options):
   app_id = config.get('app_id')
   process = subprocess.Popen(
       ['adb', 'uninstall', app_id],
@@ -215,10 +221,10 @@
   result = {}
 
   if not os.path.exists(checkout_dir):
-    with utils.ChangedWorkingDirectory(WORKING_DIR):
+    with utils.ChangedWorkingDirectory(WORKING_DIR, quiet=options.quiet):
       GitClone(git_repo)
   elif options.pull:
-    with utils.ChangedWorkingDirectory(checkout_dir):
+    with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
       # Checkout build.gradle to avoid merge conflicts.
       if IsTrackedByGit('build.gradle'):
         GitCheckout('build.gradle')
@@ -240,7 +246,7 @@
 def BuildAppWithSelectedShrinkers(app, config, options, checkout_dir, temp_dir):
   result_per_shrinker = {}
 
-  with utils.ChangedWorkingDirectory(checkout_dir):
+  with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
     for shrinker in SHRINKERS:
       if options.shrinker and shrinker not in options.shrinker:
         continue
@@ -280,15 +286,9 @@
           # Build app with gradle using -D...keepRuleSynthesisForRecompilation=
           # true.
           out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
-          extra_env_vars = {
-            'JAVA_OPTS': ' '.join([
-              '-ea:com.android.tools.r8...',
-              '-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true'
-            ])
-          }
           (apk_dest, profile_dest_dir, ext_proguard_config_file) = \
               BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
-                  temp_dir, options, extra_env_vars)
+                  temp_dir, options, keepRuleSynthesisForRecompilation=True)
           dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
           recompilation_result = {
             'apk_dest': apk_dest,
@@ -314,8 +314,9 @@
               recompiled_apk_dest = os.path.join(
                   checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
               RebuildAppWithShrinker(
-                  previous_apk, recompiled_apk_dest, ext_proguard_config_file,
-                  shrinker, min_sdk, compile_sdk, temp_dir)
+                  app, previous_apk, recompiled_apk_dest,
+                  ext_proguard_config_file, shrinker, min_sdk, compile_sdk,
+                  options, temp_dir)
               recompilation_result = {
                 'apk_dest': recompiled_apk_dest,
                 'build_status': 'success',
@@ -334,13 +335,20 @@
 
       result_per_shrinker[shrinker] = result
 
+  if not options.app:
+    print('')
+    LogResultsForApp(app, result_per_shrinker, options)
+    print('')
+
   return result_per_shrinker
 
 def BuildAppWithShrinker(
     app, config, shrinker, checkout_dir, out_dir, temp_dir, options,
-    env_vars=None):
-  print()
-  print('Building {} with {}'.format(app, shrinker))
+    keepRuleSynthesisForRecompilation=False):
+  print('Building {} with {}{}'.format(
+      app,
+      shrinker,
+      ' for recompilation' if keepRuleSynthesisForRecompilation else ''))
 
   # Add/remove 'r8.jar' from top-level build.gradle.
   if options.disable_tot:
@@ -361,11 +369,9 @@
   as_utils.SetPrintConfigurationDirective(
       app, config, checkout_dir, proguard_config_dest)
 
-  env = os.environ.copy()
+  env = {}
   env['ANDROID_HOME'] = android_home
   env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
-  if env_vars:
-    env.update(env_vars)
 
   releaseTarget = config.get('releaseTarget')
   if not releaseTarget:
@@ -381,20 +387,12 @@
          '--profile', '--stacktrace',
          '-Pandroid.enableR8=' + str(enableR8).lower(),
          '-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+  if keepRuleSynthesisForRecompilation:
+    cmd.append('-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
   if options.gradle_flags:
     cmd.extend(options.gradle_flags.split(' '))
 
-  utils.PrintCmd(cmd)
-  build_process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
-  stdout = []
-  while True:
-    line = build_process.stdout.readline()
-    if line != b'':
-      stripped = line.rstrip()
-      stdout.append(stripped)
-      print(stripped)
-    else:
-      break
+  stdout = utils.RunCmd(cmd, env, quiet=options.quiet)
 
   apk_base_name = (archives_base_name
       + (('-' + flavor) if flavor else '') + '-release')
@@ -425,25 +423,27 @@
 
   if os.path.isfile(signed_apk):
     apk_dest = os.path.join(out_dir, signed_apk_name)
-    as_utils.MoveFile(signed_apk, apk_dest)
+    as_utils.MoveFile(signed_apk, apk_dest, quiet=options.quiet)
   else:
     apk_dest = os.path.join(out_dir, unsigned_apk_name)
-    as_utils.MoveFile(unsigned_apk, apk_dest)
+    as_utils.MoveFile(unsigned_apk, apk_dest, quiet=options.quiet)
 
-  assert IsBuiltWithR8(apk_dest, temp_dir) == ('r8' in shrinker), (
+  assert IsBuiltWithR8(apk_dest, temp_dir, options) == ('r8' in shrinker), (
       'Unexpected marker in generated APK for {}'.format(shrinker))
 
   profile_dest_dir = os.path.join(out_dir, 'profile')
-  as_utils.MoveProfileReportTo(profile_dest_dir, stdout)
+  as_utils.MoveProfileReportTo(profile_dest_dir, stdout, quiet=options.quiet)
 
   return (apk_dest, profile_dest_dir, proguard_config_dest)
 
 def RebuildAppWithShrinker(
-    apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
-    temp_dir):
+    app, apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
+    options, temp_dir):
   assert 'r8' in shrinker
   assert apk_dest.endswith('.apk')
 
+  print('Rebuilding {} with {}'.format(app, shrinker))
+
   # Compile given APK with shrinker to temporary zip file.
   android_jar = utils.get_android_jar(compile_sdk)
   r8_jar = os.path.join(
@@ -462,20 +462,19 @@
     cmd.append('--lib')
     cmd.append(android_optional_jar)
 
-  utils.PrintCmd(cmd)
-
-  subprocess.check_output(cmd)
+  utils.RunCmd(cmd, quiet=options.quiet)
 
   # Make a copy of the given APK, move the newly generated dex files into the
   # copied APK, and then sign the APK.
-  apk_masseur.masseur(apk, dex=zip_dest, out=apk_dest)
+  apk_masseur.masseur(
+    apk, dex=zip_dest, out=apk_dest, quiet=options.quiet)
 
 def RunMonkey(app, config, options, apk_dest):
   if not WaitForEmulator():
     return False
 
-  UninstallApkOnEmulator(app, config)
-  InstallApkOnEmulator(apk_dest)
+  UninstallApkOnEmulator(app, config, options)
+  InstallApkOnEmulator(apk_dest, options)
 
   app_id = config.get('app_id')
   number_of_events_to_generate = options.monkey_events
@@ -486,27 +485,28 @@
 
   cmd = ['adb', 'shell', 'monkey', '-p', app_id, '-s', str(random_seed),
       str(number_of_events_to_generate)]
-  utils.PrintCmd(cmd)
 
   try:
-    stdout = subprocess.check_output(cmd)
+    stdout = utils.RunCmd(cmd, quiet=options.quiet)
     succeeded = (
         'Events injected: {}'.format(number_of_events_to_generate) in stdout)
   except subprocess.CalledProcessError as e:
     succeeded = False
 
-  UninstallApkOnEmulator(app, config)
+  UninstallApkOnEmulator(app, config, options)
 
   return succeeded
 
 def LogResultsForApps(result_per_shrinker_per_app, options):
-  for app, result_per_shrinker in result_per_shrinker_per_app.iteritems():
+  print('')
+  for app, result_per_shrinker in sorted(
+      result_per_shrinker_per_app.iteritems()):
     LogResultsForApp(app, result_per_shrinker, options)
 
 def LogResultsForApp(app, result_per_shrinker, options):
   print(app + ':')
 
-  if result_per_shrinker.get('status') != 'success':
+  if result_per_shrinker.get('status', 'success') != 'success':
     error_message = result_per_shrinker.get('error_message')
     print('  skipped ({})'.format(error_message))
     return
@@ -613,6 +613,10 @@
                     action='store_true')
   result.add_option('--gradle-flags', '--gradle_flags',
                     help='Flags to pass in to gradle')
+  result.add_option('--quiet',
+                    help='Disable verbose logging',
+                    default=False,
+                    action='store_true')
   (options, args) = result.parse_args(argv)
   if options.disable_tot:
     # r8.jar is required for recompiling the generated APK
@@ -653,7 +657,7 @@
       result_per_shrinker_per_app[options.app] = GetResultsForApp(
           options.app, APPS.get(options.app), options, temp_dir)
     else:
-      for app, config in APPS.iteritems():
+      for app, config in sorted(APPS.iteritems()):
         if not config.get('skip', False):
           result_per_shrinker_per_app[app] = GetResultsForApp(
               app, config, options, temp_dir)
diff --git a/tools/utils.py b/tools/utils.py
index 72febdc..8c37a55 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -54,13 +54,86 @@
 R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
 RETRACE_JAR = os.path.join(THIRD_PARTY, 'proguard', 'proguard6.0.1', 'lib', 'retrace.jar')
 
-def PrintCmd(s):
-  if type(s) is list:
-    s = ' '.join(s)
-  print 'Running: %s' % s
+def Print(s, quiet=False):
+  if quiet:
+    return
+  print(s)
+
+def Warn(message):
+  CRED = '\033[91m'
+  CEND = '\033[0m'
+  print(CRED + message + CEND)
+
+def PrintCmd(cmd, env=None, quiet=False):
+  if quiet:
+    return
+  if type(cmd) is list:
+    cmd = ' '.join(cmd)
+  if env:
+    env = ' '.join(['{}=\"{}\"'.format(x, y) for x, y in env.iteritems()])
+    print('Running: {} {}'.format(env, cmd))
+  else:
+    print('Running: {}'.format(cmd))
   # I know this will hit os on windows eventually if we don't do this.
   sys.stdout.flush()
 
+class ProgressLogger(object):
+  CLEAR_LINE = '\033[K'
+  UP = '\033[F'
+
+  def __init__(self, quiet=False):
+    self._count = 0
+    self._has_printed = False
+    self._quiet = quiet
+
+  def log(self, text):
+    if self._quiet:
+      if self._has_printed:
+        sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+      if len(text) > 140:
+        text = text[0:140] + '...'
+    print(text)
+    self._has_printed = True
+
+  def done(self):
+    if self._quiet and self._has_printed:
+      sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+      print('')
+      sys.stdout.write(ProgressLogger.UP)
+
+def RunCmd(cmd, env_vars=None, quiet=False):
+  PrintCmd(cmd, env=env_vars, quiet=quiet)
+  env = os.environ.copy()
+  if env_vars:
+    env.update(env_vars)
+  process = subprocess.Popen(
+      cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+  stdout = []
+  logger = ProgressLogger(quiet=quiet)
+  failed = False
+  while True:
+    line = process.stdout.readline()
+    if line != b'':
+      stripped = line.rstrip()
+      stdout.append(stripped)
+      logger.log(stripped)
+
+      # TODO(christofferqa): r8 should fail with non-zero exit code.
+      if ('AssertionError' in stripped
+          or 'CompilationError' in stripped
+          or 'CompilationFailedException' in stripped
+          or 'Compilation failed' in stripped):
+        failed = True
+    else:
+      logger.done()
+      exit_code = process.poll()
+      if exit_code or failed:
+        for line in stdout:
+          Warn(line)
+        raise subprocess.CalledProcessError(
+            exit_code, cmd, output='\n'.join(stdout))
+      return stdout
+
 def IsWindows():
   return os.name == 'nt'
 
@@ -196,16 +269,19 @@
    shutil.rmtree(self._temp_dir, ignore_errors=True)
 
 class ChangedWorkingDirectory(object):
- def __init__(self, working_directory):
+ def __init__(self, working_directory, quiet=False):
+   self._quiet = quiet
    self._working_directory = working_directory
 
  def __enter__(self):
    self._old_cwd = os.getcwd()
-   print 'Enter directory = ', self._working_directory
+   if not self._quiet:
+     print 'Enter directory:', self._working_directory
    os.chdir(self._working_directory)
 
  def __exit__(self, *_):
-   print 'Enter directory = ', self._old_cwd
+   if not self._quiet:
+     print 'Enter directory:', self._old_cwd
    os.chdir(self._old_cwd)
 
 # Reading Android CTS test_result.xml