Merge "Use android.jar corresponding to api 28 for AndroidApiLevel.P"
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 4ef2370..89542a7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -30,6 +30,7 @@
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
     this.outValue = value;
+    this.outValue.definition = this;
     for (StackValue val : ((StackValues)value).getStackValues()) {
       val.definition = this;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 6d61f58..fcb5b8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -44,6 +44,7 @@
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
     this.outValue = value;
+    this.outValue.definition = this;
     for (StackValue val : ((StackValues)value).getStackValues()) {
       val.definition = this;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 773a648..bef2765 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Deque;
@@ -34,6 +35,7 @@
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class IRCode {
 
@@ -575,7 +577,10 @@
   }
 
   private boolean verifyDefinition(Value value) {
-    assert value.definition.outValue() == value;
+    Value outValue = value.definition.outValue();
+    assert outValue == value
+        || (value instanceof StackValue
+            && Arrays.asList(((StackValues) outValue).getStackValues()).contains(value));
     return true;
   }
 
@@ -662,14 +667,15 @@
           }
           // After the throwing instruction only debug instructions and the final jump
           // instruction is allowed.
-          // TODO(ager): For now allow const instructions due to the way consts are pushed
-          // towards their use
+          // TODO(mkroghj) Temporarily allow stack-operations to be after throwing instructions.
           if (seenThrowing) {
             assert instruction.isDebugInstruction()
                 || instruction.isJumpInstruction()
-                || instruction.isConstInstruction()
                 || instruction.isStore()
-                || instruction.isPop();
+                || instruction.isPop()
+                || instruction.isDup()
+                || instruction.isDup2()
+                || instruction.isSwap();
           }
         }
       }
@@ -678,7 +684,7 @@
   }
 
   public boolean verifyNoImpreciseOrBottomTypes() {
-    return verifySSATypeLattice(
+    Predicate<Value> verifyValue =
         v -> {
           assert v.getTypeLattice().isPreciseType();
           assert !v.getTypeLattice().isFineGrainedType();
@@ -688,6 +694,16 @@
           assert !(v.definition instanceof ImpreciseMemberTypeInstruction)
               || ((ImpreciseMemberTypeInstruction) v.definition).getMemberType().isPrecise();
           return true;
+        };
+    return verifySSATypeLattice(
+        v -> {
+          // StackValues is an artificial type created to allow returning multiple values from an
+          // instruction.
+          if (v instanceof StackValues) {
+            return Stream.of(((StackValues) v).getStackValues()).allMatch(verifyValue);
+          } else {
+            return verifyValue(v);
+          }
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c010973..e235682 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -64,6 +64,24 @@
   }
 
   @Override
+  public boolean instructionInstanceCanThrow() {
+    return !(size().definition != null
+        && size().definition.isConstNumber()
+        && size().definition.asConstNumber().getRawValue() >= 0
+        && size().definition.asConstNumber().getRawValue() < Integer.MAX_VALUE);
+  }
+
+  @Override
+  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+    if (instructionInstanceCanThrow()) {
+      return false;
+    }
+    // This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo.
+    DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    return baseType.isPrimitiveType() || appInfo.definitionFor(baseType) != null;
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isNewArrayEmpty() && other.asNewArrayEmpty().type == type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index ed51913..f534fb9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -73,6 +74,19 @@
   }
 
   @Override
+  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+    if (!src().getTypeLattice().isNullable() && src().numberOfAllUsers() == 1) {
+      // The NewArrayFilledData instruction is only inserted by an R8 optimization following
+      // a NewArrayEmpty when there are more than one entry.
+      assert src().uniqueUsers().iterator().next() == this;
+      assert src().definition != null;
+      assert src().definition.isNewArrayEmpty();
+      return true;
+    }
+    return false;
+  }
+
+  @Override
   public boolean instructionTypeCanThrow() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 80edc80..3c623d6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -33,6 +33,7 @@
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
     this.outValue = value;
+    this.outValue.definition = this;
     for (StackValue val : ((StackValues)value).getStackValues()) {
       val.definition = this;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 4dcc839..6b5f6c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -166,6 +166,7 @@
         reachedFixpoint = !phiOptimizations.optimize(code);
       }
     }
+    assert code.isConsistentSSA();
     registerAllocator = new CfRegisterAllocator(code, options, typeVerificationHelper);
     registerAllocator.allocateRegisters();
     loadStoreHelper.insertPhiMoves(registerAllocator);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 7c22bbf..fd968d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -210,7 +210,15 @@
             iterator.replaceCurrentInstruction(newInvoke);
 
             if (constantReturnMaterializingInstruction != null) {
-              iterator.add(constantReturnMaterializingInstruction);
+              if (block.hasCatchHandlers()) {
+                // Split the block to ensure no instructions after throwing instructions.
+                iterator
+                    .split(code, blocks)
+                    .listIterator()
+                    .add(constantReturnMaterializingInstruction);
+              } else {
+                iterator.add(constantReturnMaterializingInstruction);
+              }
             }
 
             DexType actualReturnType = actualTarget.proto.returnType;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index be171c9..fa259c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -115,16 +115,15 @@
         continue;
       }
       Value outValue = current.outValue();
-      // Instructions with no out value cannot be dead code by the current definition
-      // (unused out value). They typically side-effect input values or deals with control-flow.
-      assert outValue != null;
-      if (!outValue.isDead(appInfo)) {
+      if (outValue != null && !outValue.isDead(appInfo)) {
         continue;
       }
       updateWorklist(worklist, current);
       // All users will be removed for this instruction. Eagerly clear them so further inspection
       // of this instruction during dead code elimination will terminate here.
-      outValue.clearUsers();
+      if (outValue != null) {
+        outValue.clearUsers();
+      }
       iterator.removeOrReplaceByDebugLocalRead();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 173de18..357e949 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -423,14 +423,13 @@
       }
     }
 
-    // Allow the first method to consume more than the allowance to be inlined.
-    return instructionAllowance < 0;
+    return instructionAllowance < Inliner.numberOfInstructions(inlinee.code);
   }
 
   @Override
   public void markInlined(InlineeWithReason inlinee) {
     // TODO(118734615): All inlining use from the budget - should that only be SIMPLE?
-    instructionAllowance -= inliner.numberOfInstructions(inlinee.code);
+    instructionAllowance -= Inliner.numberOfInstructions(inlinee.code);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 2b0d831..27ea742 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -446,7 +446,7 @@
     }
   }
 
-  final int numberOfInstructions(IRCode code) {
+  static final int numberOfInstructions(IRCode code) {
     int numOfInstructions = 0;
     for (BasicBlock block : code.blocks) {
       numOfInstructions += block.getInstructions().size();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 89eef31..ad3fb38 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,17 +17,20 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.google.common.collect.Sets;
+import java.util.ListIterator;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -113,7 +116,8 @@
   private boolean tryConstantReplacementFromProguard(
       IRCode code,
       Set<Value> affectedValues,
-      InstructionIterator iterator,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
       Instruction current,
       ProguardMemberRuleLookup lookup) {
     Instruction replacement = constantReplacementFromProguardRule(lookup.rule, code, current);
@@ -132,7 +136,11 @@
         current.outValue().replaceUsers(replacement.outValue());
       }
       replacement.setPosition(current.getPosition());
-      iterator.add(replacement);
+      if (current.getBlock().hasCatchHandlers()) {
+        iterator.split(code, blocks).listIterator().add(replacement);
+      } else {
+        iterator.add(replacement);
+      }
     }
     return true;
   }
@@ -141,7 +149,8 @@
       IRCode code,
       DexType callingContext,
       Set<Value> affectedValues,
-      InstructionIterator iterator,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
       InvokeMethod current) {
     DexMethod invokedMethod = current.getInvokedMethod();
     DexType invokedHolder = invokedMethod.getHolder();
@@ -161,7 +170,8 @@
       } else if (!outValueNullOrNotUsed) {
         // Check to see if a constant value can be assumed.
         invokeReplaced =
-            tryConstantReplacementFromProguard(code, affectedValues, iterator, current, lookup);
+            tryConstantReplacementFromProguard(
+                code, affectedValues, blocks, iterator, current, lookup);
       }
     }
     if (invokeReplaced || current.outValue() == null) {
@@ -190,7 +200,11 @@
       current.setOutValue(null);
       replacement.setPosition(current.getPosition());
       current.moveDebugValues(replacement);
-      iterator.add(replacement);
+      if (current.getBlock().hasCatchHandlers()) {
+        iterator.split(code, blocks).listIterator().add(replacement);
+      } else {
+        iterator.add(replacement);
+      }
     }
   }
 
@@ -198,7 +212,8 @@
       IRCode code,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       Set<Value> affectedValues,
-      InstructionIterator iterator,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
       StaticGet current) {
     DexField field = current.getField();
     DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
@@ -215,7 +230,8 @@
     ProguardMemberRuleLookup lookup = lookupMemberRule(target);
     if (lookup != null
         && lookup.type == RuleType.ASSUME_VALUES
-        && tryConstantReplacementFromProguard(code, affectedValues, iterator, current, lookup)) {
+        && tryConstantReplacementFromProguard(
+            code, affectedValues, blocks, iterator, current, lookup)) {
       return;
     }
     if (current.dest() != null) {
@@ -275,17 +291,26 @@
   public void rewriteWithConstantValues(
       IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    InstructionIterator iterator = code.instructionIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-      if (current.isInvokeMethod()) {
-        rewriteInvokeMethodWithConstantValues(
-            code, callingContext, affectedValues, iterator, current.asInvokeMethod());
-      } else if (current.isInstancePut() || current.isStaticPut()) {
-        rewritePutWithConstantValues(iterator, current.asFieldInstruction());
-      } else if (current.isStaticGet()) {
-        rewriteStaticGetWithConstantValues(
-            code, isProcessedConcurrently, affectedValues, iterator, current.asStaticGet());
+    ListIterator<BasicBlock> blocks = code.blocks.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      InstructionListIterator iterator = block.listIterator();
+      while (iterator.hasNext()) {
+        Instruction current = iterator.next();
+        if (current.isInvokeMethod()) {
+          rewriteInvokeMethodWithConstantValues(
+              code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
+        } else if (current.isInstancePut() || current.isStaticPut()) {
+          rewritePutWithConstantValues(iterator, current.asFieldInstruction());
+        } else if (current.isStaticGet()) {
+          rewriteStaticGetWithConstantValues(
+              code,
+              isProcessedConcurrently,
+              affectedValues,
+              blocks,
+              iterator,
+              current.asStaticGet());
+        }
       }
     }
     if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index 0572e88..abd22bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -211,12 +211,16 @@
         return "invoked from lambda created in";
       case ReferencedFrom:
         return "referenced from";
+      case ReflectiveUseFrom:
+        return "reflected from";
       case ReachableFromLiveType:
         return "reachable from";
       case ReferencedInAnnotation:
         return "referenced in annotation";
       case IsLibraryMethod:
         return "defined in library";
+      case MethodHandleUseFrom:
+        return "referenced by method handle";
       default:
         throw new Unreachable("Unexpected edge kind: " + info.edgeKind());
     }
diff --git a/src/test/examples/classmerging/ArrayTypeCollisionTest.java b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
index 9e4c9fd..c422946 100644
--- a/src/test/examples/classmerging/ArrayTypeCollisionTest.java
+++ b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
@@ -12,11 +12,11 @@
   }
 
   private static void method(A[] obj) {
-    System.out.println("In method(A[])");
+    System.out.println("In method(A[]), length: " + obj.length);
   }
 
   private static void method(B[] obj) {
-    System.out.println("In method(B[])");
+    System.out.println("In method(B[]), length: " + obj.length);
   }
 
   // A cannot be merged into B because that would lead to a collision.
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index bfffcc9..a35ce71 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1740,6 +1740,7 @@
               "lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
               match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
           .put("lang.Runtime.gc.Runtime_gc_A01", cf())
+          .put("lang.Runtime.runFinalizersOnExitZ.Runtime_runFinalizersOnExit_A01", cf())
           .put(
               "util.concurrent.AbstractExecutorService.invokeAllLjava_util_CollectionJLjava_util_concurrent_TimeUnit.AbstractExecutorService_invokeAll_A06",
               match(runtimes(Runtime.ART_V4_0_4)))
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index bb5677f..daf5568 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -182,7 +182,11 @@
   }
 
   public R8TestBuilder enableGraphInspector() {
-    CollectingGraphConsumer consumer = new CollectingGraphConsumer(null);
+    return enableGraphInspector(null);
+  }
+
+  public R8TestBuilder enableGraphInspector(GraphConsumer subConsumer) {
+    CollectingGraphConsumer consumer = new CollectingGraphConsumer(subConsumer);
     setKeptGraphConsumer(consumer);
     graphConsumer = consumer;
     return self();
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index c4151d1..688c91c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -206,7 +206,8 @@
             "  public static void main(...);",
             "}",
             "-neverinline class " + main + " {",
-            "  static void method(...);",
+            "  static classmerging.A[] method(...);",
+            "  static classmerging.B[] method(...);",
             "}"));
   }
 
diff --git a/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
new file mode 100644
index 0000000..5de7de0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
@@ -0,0 +1,92 @@
+// 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.deadcode;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Aput;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RemoveDeadArray extends TestBase {
+
+  public static class TestClassWithCatch {
+    static {
+      try {
+        int[] foobar = new int[]{42, 42, 42, 42};
+      } catch (Exception ex) {
+        System.out.println("foobar");
+      }
+    }
+  }
+
+  public static class TestClass {
+    private static int[] foobar = new int[]{42, 42, 42, 42};
+    public static void main(java.lang.String[] args) {
+      ShouldGoAway[] willBeRemoved = new ShouldGoAway[4];
+      ShouldGoAway[] willAlsoBeRemoved = new ShouldGoAway[0];
+      System.out.println("foobar");
+    }
+
+    public static class ShouldGoAway { }
+  }
+
+  private Backend backend;
+
+  public RemoveDeadArray(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    // Todo(ricow): enable unused array removal for cf backend.
+    return new Backend[]{Backend.DEX};
+  }
+
+
+  @Test
+  public void testDeadArraysRemoved() throws Exception {
+    R8TestCompileResult result =
+        testForR8(backend)
+            .addProgramClasses(TestClass.class, TestClass.ShouldGoAway.class)
+            .addKeepMainRule(TestClass.class)
+            .compile();
+    CodeInspector inspector = result.inspector();
+    assertFalse(inspector.clazz(TestClass.class).clinit().isPresent());
+
+    MethodSubject main = inspector.clazz(TestClass.class).mainMethod();
+    main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray());
+    assertFalse(main.getMethod().getCode().asDexCode().toString().contains("NewArray"));
+    runOnArt(result.app, TestClass.class.getName());
+ }
+
+  @Test
+  public void testNotRemoveStaticCatch() throws Exception {
+    R8TestCompileResult result =
+        testForR8(backend)
+            .addProgramClasses(TestClassWithCatch.class)
+            .addKeepAllClassesRule()
+            .compile();
+    CodeInspector inspector = result.inspector();
+    MethodSubject clinit = inspector.clazz(TestClassWithCatch.class).clinit();
+    assertTrue(clinit.isPresent());
+    // Ensure that our optimization does not hit, we should still have 4 Aput instructions.
+    long aPutCount = Arrays.stream(clinit.getMethod().getCode().asDexCode().instructions)
+        .filter(instruction -> instruction instanceof  Aput)
+        .count();
+    assertEquals(4, aPutCount);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
new file mode 100644
index 0000000..e8eb416
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -0,0 +1,18 @@
+// 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.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByFieldReflectionTest {
+
+  public final int foo = 42;
+
+  public static void main(String[] args) throws Exception {
+    // Due to b/123210548 the object cannot be created by a reflective newInstance call.
+    KeptByFieldReflectionTest obj = new KeptByFieldReflectionTest();
+    System.out.println("got foo: " + KeptByFieldReflectionTest.class.getField("foo").getInt(obj));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
new file mode 100644
index 0000000..6feaa35
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.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.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.fieldFromField;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByFieldReflectionTestRunner extends TestBase {
+
+  private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
+  private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+  private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
+
+  private final String EXPECTED_WHYAREYOUKEEPING =
+      StringUtils.lines(
+          "int com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.foo",
+          "|- is reflected from:",
+          "|  void com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.main(java.lang.String[])",
+          "|- is referenced in keep rule:",
+          "|  -keep class com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest { public static void main(java.lang.String[]); }");
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public KeptByFieldReflectionTestRunner(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  @Ignore("b/123215165")
+  public void test() throws Exception {
+    MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+    FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
+
+    if (backend == Backend.CF) {
+      testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+    }
+
+    WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+    GraphInspector inspector =
+        testForR8(backend)
+            .enableGraphInspector(consumer)
+            .enableInliningAnnotations()
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(CLASS)
+            .run(CLASS)
+            .assertSuccessWithOutput(EXPECTED_STDOUT)
+            .graphInspector();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    consumer.printWhyAreYouKeeping(fooField, new PrintStream(baos));
+    assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+    assertEquals(1, inspector.getRoots().size());
+    QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+    inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+    inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
new file mode 100644
index 0000000..6394237
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
@@ -0,0 +1,20 @@
+// 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.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByMethodReflectionTest {
+
+  public void foo() {
+    System.out.println("called foo");
+  }
+
+  public static void main(String[] args) throws Exception {
+    // Due to b/123210548 the object cannot be created by a reflective newInstance call.
+    KeptByMethodReflectionTest obj = new KeptByMethodReflectionTest();
+    KeptByMethodReflectionTest.class.getMethod("foo").invoke(obj);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
new file mode 100644
index 0000000..3224689
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
@@ -0,0 +1,83 @@
+// 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.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByMethodReflectionTestRunner extends TestBase {
+
+  private static final Class<?> CLASS = KeptByMethodReflectionTest.class;
+  private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+  private final String EXPECTED_STDOUT = StringUtils.lines("called foo");
+
+  private final String EXPECTED_WHYAREYOUKEEPING =
+      StringUtils.lines(
+          "void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.foo()",
+          "|- is reflected from:",
+          "|  void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.main(java.lang.String[])",
+          "|- is referenced in keep rule:",
+          "|  -keep class com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest { public static void main(java.lang.String[]); }");
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public KeptByMethodReflectionTestRunner(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+    MethodReference fooMethod = methodFromMethod(CLASS.getDeclaredMethod("foo"));
+
+    if (backend == Backend.CF) {
+      testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+    }
+
+    WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+    GraphInspector inspector =
+        testForR8(backend)
+            .enableGraphInspector(consumer)
+            .enableInliningAnnotations()
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(CLASS)
+            .run(CLASS)
+            .assertSuccessWithOutput(EXPECTED_STDOUT)
+            .graphInspector();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    consumer.printWhyAreYouKeeping(fooMethod, new PrintStream(baos));
+    assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+    assertEquals(1, inspector.getRoots().size());
+    QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+    inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+    inspector.method(fooMethod).assertRenamed().assertReflectedFrom(mainMethod);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index ee2155e..375f0b5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfMonitor;
 import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturn;
@@ -280,6 +281,11 @@
   }
 
   @Override
+  public boolean isNewArray() {
+    return instruction instanceof CfNewArray;
+  }
+
+  @Override
   public int size() {
     // TODO(b/122302789): CfInstruction#getSize()
     throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 7051226..e2e801e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -67,6 +67,7 @@
 import com.android.tools.r8.code.MulIntLit8;
 import com.android.tools.r8.code.MulLong;
 import com.android.tools.r8.code.MulLong2Addr;
+import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.code.PackedSwitch;
@@ -360,6 +361,11 @@
   }
 
   @Override
+  public boolean isNewArray() {
+    return instruction instanceof NewArray;
+  }
+
+  @Override
   public boolean isMonitorEnter() {
     return instruction instanceof MonitorEnter;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 294cc8c..dd5af7c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -82,6 +82,8 @@
 
   boolean isMultiplication();
 
+  boolean isNewArray();
+
   boolean isMonitorEnter();
 
   boolean isMonitorExit();
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 940482b..1d53013 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -41,6 +41,8 @@
   public static class EdgeKindPredicate implements Predicate<Set<GraphEdgeInfo>> {
     public static final EdgeKindPredicate keepRule = new EdgeKindPredicate(EdgeKind.KeepRule);
     public static final EdgeKindPredicate invokedFrom = new EdgeKindPredicate(EdgeKind.InvokedFrom);
+    public static final EdgeKindPredicate reflectedFrom =
+        new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
 
     private final EdgeKind edgeKind;
 
@@ -69,6 +71,8 @@
 
     abstract boolean isInvokedFrom(MethodReference method);
 
+    abstract boolean isReflectedFrom(MethodReference method);
+
     abstract boolean isKeptBy(QueryNode node);
 
     abstract String getNodeDescription();
@@ -119,6 +123,19 @@
       return this;
     }
 
+    public QueryNode assertReflectedFrom(MethodReference method) {
+      assertTrue(
+          errorMessage("reflection from " + method.toString(), "none"), isReflectedFrom(method));
+      return this;
+    }
+
+    public QueryNode assertNotReflectedFrom(MethodReference method) {
+      assertFalse(
+          errorMessage("no reflection from " + method.toString(), "reflection"),
+          isReflectedFrom(method));
+      return this;
+    }
+
     public QueryNode assertKeptBy(QueryNode node) {
       assertTrue(
           "Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
@@ -174,6 +191,12 @@
     }
 
     @Override
+    public boolean isReflectedFrom(MethodReference method) {
+      fail("Invalid call to isReflectedFrom on " + getNodeDescription());
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean isKeptBy(QueryNode node) {
       fail("Invalid call to isKeptBy on " + getNodeDescription());
       throw new Unreachable();
@@ -238,6 +261,18 @@
     }
 
     @Override
+    public boolean isReflectedFrom(MethodReference method) {
+      GraphNode sourceMethod = inspector.methods.get(method);
+      if (sourceMethod == null) {
+        return false;
+      }
+      return filterSources(
+              (node, infos) -> node == sourceMethod && EdgeKindPredicate.reflectedFrom.test(infos))
+          .findFirst()
+          .isPresent();
+    }
+
+    @Override
     public boolean isKeptBy(QueryNode node) {
       if (!(node instanceof QueryNodeImpl)) {
         return false;
@@ -356,6 +391,10 @@
     return getQueryNode(methods.get(method), method.toString());
   }
 
+  public QueryNode field(FieldReference field) {
+    return getQueryNode(fields.get(field), field.toString());
+  }
+
   private QueryNode getQueryNode(GraphNode node, String absentString) {
     return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
   }
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 93c910e..b4eb396 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -66,6 +66,44 @@
       if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
         f.write(line)
 
+def GetMinAndCompileSdk(app, config, checkout_dir, apk_reference):
+  app_module = config.get('app_module', 'app')
+  build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle')
+  assert os.path.isfile(build_gradle_file), (
+      'Expected to find build.gradle file at {}'.format(build_gradle_file))
+
+  compile_sdk = None
+  min_sdks = []
+  target_sdk = None
+
+  with open(build_gradle_file) as f:
+    for line in f.readlines():
+      stripped = line.strip()
+      if stripped.startswith('compileSdkVersion '):
+        assert not compile_sdk
+        compile_sdk = int(stripped[len('compileSdkVersion '):])
+      if stripped.startswith('minSdkVersion '):
+        min_sdks.append(int(stripped[len('minSdkVersion '):]))
+      elif stripped.startswith('targetSdkVersion '):
+        assert not target_sdk
+        target_sdk = int(stripped[len('targetSdkVersion '):])
+
+  if len(min_sdks) == 1:
+    min_sdk = min_sdks[0]
+  else:
+    assert 'min_sdk' in config
+    min_sdk = config.get('min_sdk')
+
+  assert min_sdk, (
+      'Expected to find `minSdkVersion` in {}'.format(build_gradle_file))
+  assert compile_sdk, (
+      'Expected to find `compileSdkVersion` in {}'.format(build_gradle_file))
+
+  assert not target_sdk or target_sdk == compile_sdk, (
+      'Expected `compileSdkVersion` and `targetSdkVersion` to be the same')
+
+  return (min_sdk, compile_sdk)
+
 def IsGradleTaskName(x):
   # Check that it is non-empty.
   if not x:
diff --git a/tools/compare_apk_sizes.py b/tools/compare_apk_sizes.py
index a8137c2..5e57ad5 100755
--- a/tools/compare_apk_sizes.py
+++ b/tools/compare_apk_sizes.py
@@ -11,6 +11,8 @@
 import os
 import shutil
 import sys
+import threading
+import time
 import toolhelper
 import utils
 import zipfile
@@ -19,6 +21,8 @@
 USAGE = """%prog [options] app1 app2
   NOTE: This only makes sense if minification is disabled"""
 
+MAX_THREADS=40
+
 def parse_options():
   result = optparse.OptionParser(usage=USAGE)
   result.add_option('--temp',
@@ -68,9 +72,14 @@
   return 0
 
 class FileInfo:
-  def __init__(self, path, root, use_code_size):
+  def __init__(self, path, root):
     self.path = path
     self.full_path = os.path.join(root, path)
+
+  def __eq__(self, other):
+    return self.full_path == other.full_path
+
+  def set_size(self, use_code_size):
     if use_code_size:
       self.size = get_code_size(self.full_path)
     else:
@@ -83,8 +92,27 @@
       for f in files:
         assert f.endswith('dex')
         file_path = os.path.join(root, f)
-        entry = FileInfo(file_path, path, use_code_size=options.use_code_size)
+        entry = FileInfo(file_path, path)
+        if not options.use_code_size:
+          entry.set_size()
         file_info_map[file_path] = entry
+  threads = []
+  file_infos = file_info_map.values() if options.use_code_size else []
+  while len(file_infos) > 0 or len(threads)> 0:
+    for t in threads:
+      if not t.is_alive():
+        threads.remove(t)
+    # sleep
+    if len(threads) == MAX_THREADS or len(file_infos) == 0:
+      time.sleep(0.5)
+    while len(threads) < MAX_THREADS and len(file_infos) > 0:
+      info = file_infos.pop()
+      print('Added %s for size calculation' % info.full_path)
+      t = threading.Thread(target=info.set_size, args=(options.use_code_size,))
+      threads.append(t)
+      t.start()
+    print('Missing %s files, threads=%s ' % (len(file_infos), len(threads)))
+
   return file_info_map
 
 def print_info(app, app_files, only_in_app, bigger_in_app, output):
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index a74f868..e9b2dcd 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -78,6 +78,7 @@
       'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
       'flavor': 'standard',
       'releaseTarget': 'app:assembleRelease',
+      'min_sdk': 16
   },
   'tivi': {
       'app_id': 'app.tivi',
@@ -292,6 +293,10 @@
             with open(proguard_config_file) as old:
               assert(sum(1 for line in new) > sum(1 for line in old))
 
+          # Extract min-sdk and target-sdk
+          (min_sdk, compile_sdk) = as_utils.GetMinAndCompileSdk(app, config,
+              checkout_dir, apk_dest)
+
           # Now rebuild generated apk.
           previous_apk = apk_dest
           for i in range(1, options.r8_compilation_steps):
@@ -300,7 +305,7 @@
                   checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
               RebuildAppWithShrinker(
                   previous_apk, recompiled_apk_dest, ext_proguard_config_file,
-                  shrinker)
+                  shrinker, min_sdk, compile_sdk)
               recompilation_result = {
                 'apk_dest': recompiled_apk_dest,
                 'build_status': 'success',
@@ -422,18 +427,24 @@
 
   return (apk_dest, profile_dest_dir, proguard_config_dest)
 
-def RebuildAppWithShrinker(apk, apk_dest, proguard_config_file, shrinker):
+def RebuildAppWithShrinker(
+    apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk):
   assert 'r8' in shrinker
   assert apk_dest.endswith('.apk')
 
   # Compile given APK with shrinker to temporary zip file.
-  api = 28 # TODO(christofferqa): Should be the one from build.gradle
-  android_jar = os.path.join(utils.REPO_ROOT, utils.ANDROID_JAR.format(api=api))
+  android_jar = os.path.join(
+      utils.REPO_ROOT,
+      utils.ANDROID_JAR.format(api=compile_sdk))
   r8_jar = utils.R8LIB_JAR if IsMinifiedR8(shrinker) else utils.R8_JAR
   zip_dest = apk_dest[:-4] + '.zip'
 
-  cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar,
-      'com.android.tools.r8.R8', '--release', '--pg-conf', proguard_config_file,
+  # TODO(christofferqa): Entry point should be CompatProguard if the shrinker
+  # is 'r8'.
+  entry_point = 'com.android.tools.r8.R8'
+
+  cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar, entry_point,
+      '--release', '--min-api', str(min_sdk), '--pg-conf', proguard_config_file,
       '--lib', android_jar, '--output', zip_dest, apk]
   utils.PrintCmd(cmd)