Merge "Make minimal main-dex dependent on compilation mode"
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index 840efae..e5d2952 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -10,7 +10,7 @@
 
 abstract class Format10t extends Base1Format {
 
-  public final /* offset */ byte AA;
+  public /* offset */ byte AA;
 
   // +AA | op
   Format10t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index 89d740b..b3e3627 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -10,7 +10,7 @@
 
 abstract class Format20t extends Base2Format {
 
-  public final /* offset */ short AAAA;
+  public /* offset */ short AAAA;
 
   // øø | op | +AAAA
   Format20t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 12b752b..26f7650 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -11,10 +11,10 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
-abstract class Format21t extends Base2Format {
+public abstract class Format21t extends Base2Format {
 
   public final short AA;
-  public final /* offset */ short BBBB;
+  public /* offset */ short BBBB;
 
   // AA | op | +BBBB
   Format21t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 6cd51d3..3ff5190 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -11,11 +11,11 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
-abstract class Format22t extends Base2Format {
+public abstract class Format22t extends Base2Format {
 
   public final byte A;
   public final byte B;
-  public final /* offset */ short CCCC;
+  public /* offset */ short CCCC;
 
   // vB | vA | op | +CCCC
   Format22t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index 927c879..77cb49d 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -10,7 +10,7 @@
 
 abstract class Format30t extends Base3Format {
 
-  public final /* offset */ int AAAAAAAA;
+  public /* offset */ int AAAAAAAA;
 
   // øø | op | AAAAlo | AAAAhi
   Format30t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 8820b4d..2553650 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -158,13 +158,13 @@
         } catch (IOException e) {
           throw new RuntimeException(e);
         }
+        builder.addToMainDexList(
+            inputApp.getMainDexClasses()
+                .stream()
+                .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
+                .collect(Collectors.toList()));
       }));
     }
-    builder.addToMainDexList(
-        inputApp.getMainDexClasses()
-            .stream()
-            .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
-            .collect(Collectors.toList()));
   }
 
   private final class ClassReader {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index bdcbafe..a8f56a7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -41,7 +41,6 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramClassVisitor;
-import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -153,14 +152,16 @@
     return this;
   }
 
-  private void rewriteCodeWithJumboStrings(IRConverter converter, DexEncodedMethod method) {
+  private void rewriteCodeWithJumboStrings(DexEncodedMethod method) {
     if (method.getCode() == null) {
       return;
     }
     DexCode code = method.getCode().asDexCode();
     if (code.highestSortingString != null) {
       if (mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
-        converter.processJumboStrings(method, mapping.getFirstJumboString());
+        JumboStringRewriter rewriter =
+            new JumboStringRewriter(method, mapping.getFirstJumboString(), options.itemFactory);
+        rewriter.rewrite();
       }
     }
   }
@@ -176,9 +177,8 @@
       return this;
     }
     // At least one method needs a jumbo string.
-    IRConverter converter = new IRConverter(application, appInfo, options, false);
     for (DexProgramClass clazz : classes) {
-      clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(converter, method));
+      clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(method));
     }
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
new file mode 100644
index 0000000..4b97c51
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -0,0 +1,568 @@
+// Copyright (c) 2017, 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.dex;
+
+import static com.android.tools.r8.graph.DexCode.TryHandler.NO_HANDLER;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.FillArrayDataPayload;
+import com.android.tools.r8.code.Format21t;
+import com.android.tools.r8.code.Format22t;
+import com.android.tools.r8.code.Format31t;
+import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.Goto16;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfGe;
+import com.android.tools.r8.code.IfGez;
+import com.android.tools.r8.code.IfGt;
+import com.android.tools.r8.code.IfGtz;
+import com.android.tools.r8.code.IfLe;
+import com.android.tools.r8.code.IfLez;
+import com.android.tools.r8.code.IfLt;
+import com.android.tools.r8.code.IfLtz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.SwitchPayload;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class JumboStringRewriter {
+
+  private static class TryTargets {
+    private Instruction start;
+    private Instruction end;
+    private boolean endsAfterLastInstruction;
+
+    TryTargets(Instruction start, Instruction end, boolean endsAfterLastInstruction) {
+      assert start != null;
+      assert end != null;
+      this.start = start;
+      this.end = end;
+      this.endsAfterLastInstruction = endsAfterLastInstruction;
+    }
+
+    void replaceTarget(Instruction target, Instruction newTarget) {
+      if (start == target) {
+        start = newTarget;
+      }
+      if (end == target) {
+        end = newTarget;
+      }
+    }
+
+    int getStartOffset() {
+      return start.getOffset();
+    }
+
+    int getStartToEndDelta() {
+      if (endsAfterLastInstruction) {
+        return end.getOffset() + end.getSize() - start.getOffset();
+      }
+      return end.getOffset() - start.getOffset();
+    }
+  }
+
+  private final DexEncodedMethod method;
+  private final DexString firstJumboString;
+  private final DexItemFactory factory;
+  private final Map<Instruction, List<Instruction>> instructionTargets = new IdentityHashMap<>();
+  private final Int2ReferenceMap<Instruction> debugEventTargets
+      = new Int2ReferenceOpenHashMap<>();
+  private final Map<Instruction, Instruction> payloadToSwitch = new IdentityHashMap<>();
+  private final Map<Try, TryTargets> tryTargets = new IdentityHashMap<>();
+  private final Map<TryHandler, List<Instruction>> handlerTargets = new IdentityHashMap<>();
+
+  public JumboStringRewriter(
+      DexEncodedMethod method, DexString firstJumboString, DexItemFactory factory) {
+    this.method = method;
+    this.firstJumboString = firstJumboString;
+    this.factory = factory;
+  }
+
+  public void rewrite() {
+    // Build maps from everything in the code that uses offsets or direct addresses to reference
+    // instructions to the actual instruction referenced.
+    recordTargets();
+    // Expand the code by rewriting jumbo strings and branching instructions.
+    List<Instruction> newInstructions = expandCode();
+    // Commit to the new instruction offsets and update instructions, try-catch structures
+    // and debug info with the new offsets.
+    rewriteInstructionOffsets(newInstructions);
+    Try[] newTries = rewriteTryOffsets();
+    TryHandler[] newHandlers = rewriteHandlerOffsets();
+    DexDebugInfo newDebugInfo = rewriteDebugInfoOffsets();
+    // Set the new code on the method.
+    DexCode code = method.getCode().asDexCode();
+    method.setDexCode(new DexCode(
+        code.registerSize,
+        code.incomingRegisterSize,
+        code.outgoingRegisterSize,
+        newInstructions.toArray(new Instruction[newInstructions.size()]),
+        newTries,
+        newHandlers,
+        newDebugInfo,
+        code.highestSortingString));
+  }
+
+  private void rewriteInstructionOffsets(List<Instruction> instructions) {
+    for (Instruction instruction : instructions) {
+      if (instruction instanceof Format22t) {  // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+        Format22t condition = (Format22t) instruction;
+        int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+        assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+        condition.CCCC = (short) offset;
+      } else if (instruction instanceof Format21t) {  // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+        Format21t condition = (Format21t) instruction;
+        int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+        assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+        condition.BBBB = (short) offset;
+      } else if (instruction instanceof Goto) {
+        Goto jump = (Goto) instruction;
+        int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+        assert Byte.MIN_VALUE <= offset && offset <= Byte.MAX_VALUE;
+        jump.AA = (byte) offset;
+      } else if (instruction instanceof Goto16) {
+        Goto16 jump = (Goto16) instruction;
+        int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+        assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+        jump.AAAA = (short) offset;
+      } else if (instruction instanceof Goto32) {
+        Goto32 jump = (Goto32) instruction;
+        int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+        jump.AAAAAAAA = offset;
+      } else if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+        Format31t payloadUser = (Format31t) instruction;
+        int offset =
+            instructionTargets.get(payloadUser).get(0).getOffset() - instruction.getOffset();
+        payloadUser.setPayloadOffset(offset);
+      } else if (instruction instanceof SwitchPayload) {
+        SwitchPayload payload = (SwitchPayload) instruction;
+        Instruction switchInstruction = payloadToSwitch.get(payload);
+        List<Instruction> switchTargets = instructionTargets.get(payload);
+        int[] targets = payload.switchTargetOffsets();
+        for (int i = 0; i < switchTargets.size(); i++) {
+          Instruction target = switchTargets.get(i);
+          targets[i] = target.getOffset() - switchInstruction.getOffset();
+        }
+      }
+    }
+  }
+
+  private Try[] rewriteTryOffsets() {
+    DexCode code = method.getCode().asDexCode();
+    Try[] result = new Try[code.tries.length];
+    for (int i = 0; i < code.tries.length; i++) {
+      Try theTry = code.tries[i];
+      TryTargets targets = tryTargets.get(theTry);
+      result[i] = new Try(targets.getStartOffset(), targets.getStartToEndDelta(), -1);
+      result[i].handlerIndex = theTry.handlerIndex;
+    }
+    return result;
+  }
+
+  private TryHandler[] rewriteHandlerOffsets() {
+    DexCode code = method.getCode().asDexCode();
+    if (code.handlers == null) {
+      return null;
+    }
+    TryHandler[] result = new TryHandler[code.handlers.length];
+    for (int i = 0; i < code.handlers.length; i++) {
+      TryHandler handler = code.handlers[i];
+      List<Instruction> targets = handlerTargets.get(handler);
+      Iterator<Instruction> it = targets.iterator();
+      int catchAllAddr = NO_HANDLER;
+      if (handler.catchAllAddr != NO_HANDLER) {
+        catchAllAddr = it.next().getOffset();
+      }
+      TypeAddrPair[] newPairs = new TypeAddrPair[handler.pairs.length];
+      for (int j = 0; j < handler.pairs.length; j++) {
+        TypeAddrPair pair = handler.pairs[j];
+        newPairs[j] = new TypeAddrPair(pair.type, it.next().getOffset());
+      }
+      result[i] = new TryHandler(newPairs, catchAllAddr);
+    }
+    return result;
+  }
+
+  private DexDebugInfo rewriteDebugInfoOffsets() {
+    DexCode code = method.getCode().asDexCode();
+    if (debugEventTargets.size() != 0) {
+      int lastOriginalOffset = 0;
+      int lastNewOffset = 0;
+      List<DexDebugEvent> events = new ArrayList<>();
+      for (DexDebugEvent event : code.getDebugInfo().events) {
+        if (event instanceof AdvancePC) {
+          AdvancePC advance = (AdvancePC) event;
+          lastOriginalOffset += advance.delta;
+          Instruction target = debugEventTargets.get(lastOriginalOffset);
+          int pcDelta = target.getOffset() - lastNewOffset;
+          addAdvancementEvents(0, pcDelta, events);
+          lastNewOffset = target.getOffset();
+        } else if (event instanceof Default) {
+          Default defaultEvent = (Default) event;
+          lastOriginalOffset += defaultEvent.getPCDelta();
+          Instruction target = debugEventTargets.get(lastOriginalOffset);
+          int lineDelta = defaultEvent.getLineDelta();
+          int pcDelta = target.getOffset() - lastNewOffset;
+          addAdvancementEvents(lineDelta, pcDelta, events);
+          lastNewOffset = target.getOffset();
+        } else {
+          events.add(event);
+        }
+      }
+      return new DexDebugInfo(
+          code.getDebugInfo().startLine,
+          code.getDebugInfo().parameters,
+          events.toArray(new DexDebugEvent[events.size()]));
+    }
+    return code.getDebugInfo();
+  }
+
+  private void addAdvancementEvents(int lineDelta, int pcDelta, List<DexDebugEvent> events) {
+    if (lineDelta < Constants.DBG_LINE_BASE
+        || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
+      events.add(factory.createAdvanceLine(lineDelta));
+      lineDelta = 0;
+      if (pcDelta == 0) {
+        return;
+      }
+    }
+    if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
+      events.add(factory.createAdvancePC(pcDelta));
+      pcDelta = 0;
+      if (lineDelta == 0) {
+        return;
+      }
+    }
+    int specialOpcode =
+        0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
+    assert specialOpcode >= 0x0a;
+    assert specialOpcode <= 0xff;
+    events.add(factory.createDefault(specialOpcode));
+  }
+
+  private List<Instruction> expandCode() {
+    LinkedList<Instruction> instructions = new LinkedList<>();
+    Collections.addAll(instructions, method.getCode().asDexCode().instructions);
+    int offsetDelta;
+    do {
+      ListIterator<Instruction> it = instructions.listIterator();
+      offsetDelta = 0;
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        instruction.setOffset(instruction.getOffset() + offsetDelta);
+        if (instruction instanceof ConstString) {
+          ConstString string = (ConstString) instruction;
+          if (string.getString().compareTo(firstJumboString) >= 0) {
+            ConstStringJumbo jumboString = new ConstStringJumbo(string.AA, string.getString());
+            jumboString.setOffset(string.getOffset());
+            offsetDelta++;
+            it.set(jumboString);
+            replaceTarget(instruction, jumboString);
+          }
+        } else if (instruction instanceof Format22t) {  // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+          Format22t condition = (Format22t) instruction;
+          int offset =
+              instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+          if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+            Format22t newCondition = null;
+            switch (condition.getType().inverted()) {
+              case EQ:
+                newCondition = new IfEq(condition.A, condition.B, 0);
+                break;
+              case GE:
+                newCondition = new IfGe(condition.A, condition.B, 0);
+                break;
+              case GT:
+                newCondition = new IfGt(condition.A, condition.B, 0);
+                break;
+              case LE:
+                newCondition = new IfLe(condition.A, condition.B, 0);
+                break;
+              case LT:
+                newCondition = new IfLt(condition.A, condition.B, 0);
+                break;
+              case NE:
+                newCondition = new IfNe(condition.A, condition.B, 0);
+                break;
+            }
+            offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+          }
+        } else if (instruction instanceof Format21t) {  // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+          Format21t condition = (Format21t) instruction;
+          int offset =
+              instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+          if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+            Format21t newCondition = null;
+            switch (condition.getType().inverted()) {
+              case EQ:
+                newCondition = new IfEqz(condition.AA, 0);
+                break;
+              case GE:
+                newCondition = new IfGez(condition.AA, 0);
+                break;
+              case GT:
+                newCondition = new IfGtz(condition.AA, 0);
+                break;
+              case LE:
+                newCondition = new IfLez(condition.AA, 0);
+                break;
+              case LT:
+                newCondition = new IfLtz(condition.AA, 0);
+                break;
+              case NE:
+                newCondition = new IfNez(condition.AA, 0);
+                break;
+            }
+            offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+          }
+        } else if (instruction instanceof Goto) {
+          Goto jump = (Goto) instruction;
+          int offset =
+              instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+          if (Byte.MIN_VALUE > offset || offset > Byte.MAX_VALUE) {
+            Instruction newJump;
+            if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+              newJump = new Goto32(offset);
+            } else {
+              newJump = new Goto16(offset);
+            }
+            newJump.setOffset(jump.getOffset());
+            it.set(newJump);
+            offsetDelta += (newJump.getSize() - jump.getSize());
+            replaceTarget(jump, newJump);
+            List<Instruction> targets = instructionTargets.remove(jump);
+            instructionTargets.put(newJump, targets);
+          }
+        } else if (instruction instanceof Goto16) {
+          Goto16 jump = (Goto16) instruction;
+          int offset =
+              instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+          if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+            Instruction newJump = new Goto32(offset);
+            newJump.setOffset(jump.getOffset());
+            it.set(newJump);
+            offsetDelta += (newJump.getSize() - jump.getSize());
+            replaceTarget(jump, newJump);
+            List<Instruction> targets = instructionTargets.remove(jump);
+            instructionTargets.put(newJump, targets);
+          }
+        } else if (instruction instanceof Goto32) {
+          // Instruction big enough for any offset.
+        } else if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+          // Instruction big enough for any offset.
+        } else if (instruction instanceof SwitchPayload
+            || instruction instanceof FillArrayDataPayload) {
+          if (instruction.getOffset() % 2 != 0) {
+            offsetDelta++;
+            it.previous();
+            Nop nop = new Nop();
+            nop.setOffset(instruction.getOffset());
+            it.add(nop);
+            it.next();
+            instruction.setOffset(instruction.getOffset() + 1);
+          }
+          // Instruction big enough for any offset.
+        }
+      }
+    } while (offsetDelta > 0);
+    return instructions;
+  }
+
+  private int rewriteIfToIfAndGoto(
+      int offsetDelta,
+      ListIterator<Instruction> it,
+      Instruction condition,
+      Instruction newCondition) {
+    int jumpOffset = condition.getOffset() + condition.getSize();
+    Goto32 jump = new Goto32(0);
+    jump.setOffset(jumpOffset);
+    newCondition.setOffset(condition.getOffset());
+    it.set(newCondition);
+    replaceTarget(condition, newCondition);
+    it.add(jump);
+    offsetDelta += jump.getSize();
+    instructionTargets.put(jump, instructionTargets.remove(condition));
+    Instruction fallthroughInstruction = it.next();
+    instructionTargets.put(newCondition, Lists.newArrayList(fallthroughInstruction));
+    it.previous();
+    return offsetDelta;
+  }
+
+  private void replaceTarget(Instruction target, Instruction newTarget) {
+    for (List<Instruction> instructions : instructionTargets.values()) {
+      instructions.replaceAll((i) -> i == target ? newTarget : i);
+    }
+    for (Int2ReferenceMap.Entry<Instruction> entry : debugEventTargets.int2ReferenceEntrySet()) {
+      if (entry.getValue() == target) {
+        entry.setValue(newTarget);
+      }
+    }
+    for (Entry<Try, TryTargets> entry : tryTargets.entrySet()) {
+      entry.getValue().replaceTarget(target, newTarget);
+    }
+    for (List<Instruction> instructions : handlerTargets.values()) {
+      instructions.replaceAll((i) -> i == target ? newTarget : i);
+    }
+  }
+
+  private void recordInstructionTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+    Instruction[] instructions = method.getCode().asDexCode().instructions;
+    for (Instruction instruction : instructions) {
+      if (instruction instanceof Format22t) {  // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+        Format22t condition = (Format22t) instruction;
+        Instruction target = offsetToInstruction.get(condition.getOffset() + condition.CCCC);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Format21t) {  // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+        Format21t condition = (Format21t) instruction;
+        Instruction target = offsetToInstruction.get(condition.getOffset() + condition.BBBB);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Goto) {
+        Goto jump = (Goto) instruction;
+        Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AA);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Goto16) {
+        Goto16 jump = (Goto16) instruction;
+        Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAA);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Goto32) {
+        Goto32 jump = (Goto32) instruction;
+        Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAAAAAA);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+        Format31t offsetInstruction = (Format31t) instruction;
+        Instruction target = offsetToInstruction.get(
+            offsetInstruction.getOffset() + offsetInstruction.getPayloadOffset());
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof SwitchPayload) {
+        SwitchPayload payload = (SwitchPayload) instruction;
+        int[] targetOffsets = payload.switchTargetOffsets();
+        int switchOffset = payloadToSwitch.get(instruction).getOffset();
+        List<Instruction> targets = new ArrayList<>();
+        for (int i = 0; i < targetOffsets.length; i++) {
+          Instruction target = offsetToInstruction.get(switchOffset + targetOffsets[i]);
+          assert target != null;
+          targets.add(target);
+        }
+        instructionTargets.put(instruction, targets);
+      }
+    }
+  }
+
+  private void recordDebugEventTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+    DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
+    if (debugInfo != null) {
+      int address = 0;
+      for (DexDebugEvent event : debugInfo.events) {
+        if (event instanceof AdvancePC) {
+          AdvancePC advance = (AdvancePC) event;
+          address += advance.delta;
+          Instruction target = offsetToInstruction.get(address);
+          assert target != null;
+          debugEventTargets.put(address, target);
+        } else if (event instanceof Default) {
+          Default defaultEvent = (Default) event;
+          address += defaultEvent.getPCDelta();
+          Instruction target = offsetToInstruction.get(address);
+          assert target != null;
+          debugEventTargets.put(address, target);
+        }
+      }
+    }
+  }
+
+  private void recordTryAndHandlerTargets(
+      Int2ReferenceMap<Instruction> offsetToInstruction,
+      Instruction lastInstruction) {
+    DexCode code = method.getCode().asDexCode();
+    for (Try theTry : code.tries) {
+      Instruction start = offsetToInstruction.get(theTry.startAddress);
+      int endAddress = theTry.startAddress + theTry.instructionCount;
+      TryTargets targets;
+      if (endAddress > lastInstruction.getOffset()) {
+        targets = new TryTargets(start, lastInstruction, true);
+      } else {
+        Instruction end = offsetToInstruction.get(endAddress);
+        targets = new TryTargets(start, end, false);
+      }
+      assert theTry.startAddress == targets.getStartOffset();
+      assert theTry.instructionCount == targets.getStartToEndDelta();
+      tryTargets.put(theTry, targets);
+    }
+    if (code.handlers != null) {
+      for (TryHandler handler : code.handlers) {
+        List<Instruction> targets = new ArrayList<>();
+        if (handler.catchAllAddr != NO_HANDLER) {
+          Instruction target = offsetToInstruction.get(handler.catchAllAddr);
+          assert target != null;
+          targets.add(target);
+        }
+        for (TypeAddrPair pair : handler.pairs) {
+          Instruction target = offsetToInstruction.get(pair.addr);
+          assert target != null;
+          targets.add(target);
+        }
+        handlerTargets.put(handler, targets);
+      }
+    }
+  }
+
+  private void recordTargets() {
+    Int2ReferenceMap<Instruction> offsetToInstruction = new Int2ReferenceOpenHashMap<>();
+    Instruction[] instructions = method.getCode().asDexCode().instructions;
+    boolean containsPayloads = false;
+    for (Instruction instruction : instructions) {
+      offsetToInstruction.put(instruction.getOffset(), instruction);
+      if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+        containsPayloads = true;
+      }
+    }
+    if (containsPayloads) {
+      for (Instruction instruction : instructions) {
+        if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+          Instruction payload =
+              offsetToInstruction.get(instruction.getOffset() + instruction.getPayloadOffset());
+          assert payload != null;
+          payloadToSwitch.put(payload, instruction);
+        }
+      }
+    }
+    recordInstructionTargets(offsetToInstruction);
+    recordDebugEventTargets(offsetToInstruction);
+    Instruction lastInstruction = instructions[instructions.length - 1];
+    recordTryAndHandlerTargets(offsetToInstruction, lastInstruction);
+  }
+}
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 a2edd92..7915df3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -314,7 +314,7 @@
 
     public static final int NO_INDEX = -1;
 
-    private final int handlerOffset;
+    public final int handlerOffset;
     public /* offset */ int startAddress;
     public /* offset */ int instructionCount;
     public int handlerIndex;
@@ -378,7 +378,7 @@
     public static final int NO_HANDLER = -1;
 
     public final TypeAddrPair[] pairs;
-    public /* offset */ int catchAllAddr;
+    public final /* offset */ int catchAllAddr;
 
     public TryHandler(TypeAddrPair[] pairs, int catchAllAddr) {
       this.pairs = pairs;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 572e655..db99359 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -31,10 +31,9 @@
 
   public abstract void addToBuilder(DexDebugEntryBuilder builder);
 
-
   public static class AdvancePC extends DexDebugEvent {
 
-    final int delta;
+    public final int delta;
 
     public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
       writer.putByte(Constants.DBG_ADVANCE_PC);
@@ -350,6 +349,16 @@
       builder.setPosition(address, line);
     }
 
+    public int getPCDelta() {
+      int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+      return adjustedOpcode / Constants.DBG_LINE_RANGE;
+    }
+
+    public int getLineDelta() {
+      int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+      return Constants.DBG_LINE_BASE + (adjustedOpcode % Constants.DBG_LINE_RANGE);
+    }
+
     public String toString() {
       return "DEFAULT " + value;
     }
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 700e45a..e66b984 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -171,14 +171,6 @@
     code = builder.build(method.getArity());
   }
 
-  // Replaces the dex code in the method by setting code to result of compiling the IR.
-  public void setCode(IRCode ir, RegisterAllocator registerAllocator,
-      DexItemFactory dexItemFactory, DexString firstJumboString) {
-    final DexBuilder builder =
-        new DexBuilder(ir, registerAllocator, dexItemFactory, firstJumboString);
-    code = builder.build(method.getArity());
-  }
-
   public String toString() {
     return "Encoded method " + method;
   }
@@ -206,6 +198,10 @@
     return code;
   }
 
+  public void setDexCode(DexCode code) {
+    this.code = code;
+  }
+
   public void removeCode() {
     code = null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 82db97a..bb1ff32 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.code.ConstStringJumbo;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -30,11 +29,7 @@
   public void buildDex(DexBuilder builder) {
     builder.registerStringReference(value);
     int dest = builder.allocatedRegister(dest(), getNumber());
-    if (builder.isJumboString(value)) {
-      builder.add(this, new ConstStringJumbo(dest, value));
-    } else {
-      builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
-    }
+    builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 737dc46..da067d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -85,9 +85,6 @@
   // List of generated FillArrayData dex instructions.
   private final List<FillArrayDataInfo> fillArrayDataInfos = new ArrayList<>();
 
-  // First jumbo string if known.
-  private final DexString firstJumboString;
-
   // Set of if instructions that have offsets that are so large that they cannot be encoded in
   // the if instruction format.
   private Set<BasicBlock> ifsNeedingRewrite = Sets.newIdentityHashSet();
@@ -116,18 +113,6 @@
     this.ir = ir;
     this.registerAllocator = registerAllocator;
     this.dexItemFactory = dexItemFactory;
-    this.firstJumboString = null;
-  }
-
-  public DexBuilder(IRCode ir, RegisterAllocator registerAllocator,
-      DexItemFactory dexItemFactory, DexString firstJumboString) {
-    assert ir != null;
-    assert registerAllocator != null;
-    assert dexItemFactory != null;
-    this.ir = ir;
-    this.registerAllocator = registerAllocator;
-    this.dexItemFactory = dexItemFactory;
-    this.firstJumboString = firstJumboString;
   }
 
   private void reset() {
@@ -143,15 +128,6 @@
     nextBlock = null;
   }
 
-  public boolean isJumboString(DexString string) {
-    if (firstJumboString == null) {
-      return false;
-    }
-    // We have to use compareTo here, as slowCompareTo will return the wrong order when minification
-    // is used.
-    return firstJumboString.compareTo(string) <= 0;
-  }
-
   /**
    * Build the dex instructions added to this builder.
    *
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 659a503..6a4777d 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
@@ -325,10 +325,6 @@
     return builder.build();
   }
 
-  public void processJumboStrings(DexEncodedMethod method, DexString firstJumboString) {
-    convertMethodJumboStringsOnly(method, firstJumboString);
-  }
-
   private void clearDexMethodCompilationState() {
     application.classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -534,44 +530,6 @@
     }
   }
 
-  // Convert a method ensuring that strings sorting equal or higher than the argument
-  // firstJumboString are encoded as jumbo strings.
-  // TODO(sgjesse): Consider replacing this with a direct dex2dex converter instead of going
-  // through IR.
-  private void convertMethodJumboStringsOnly(
-      DexEncodedMethod method, DexString firstJumboString) {
-    // This is only used for methods already converted to Dex, but missing jumbo strings.
-    assert method.getCode() != null && method.getCode().isDexCode();
-    if (options.verbose) {
-      System.out.println("Processing jumbo strings: " + method.toSourceString());
-    }
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Original code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
-    }
-    IRCode code = method.buildIR(options);
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s",
-          method.toSourceString(), code);
-    }
-    // Compilation header if printing CFGs for this method.
-    printC1VisualizerHeader(method);
-    printMethod(code, "Initial IR (SSA)");
-
-    // Methods passed through here should have been through IR processing already and
-    // therefore, we skip most of the IR processing.
-
-    // Perform register allocation.
-    RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, registerAllocator, appInfo.dexItemFactory, firstJumboString);
-
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Resulting dex code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
-    }
-    printMethod(code, "Final IR (non-SSA)");
-  }
-
   private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
new file mode 100644
index 0000000..54302db
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardCheckDiscardRule extends ProguardConfigurationRule {
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    private Builder() {
+    }
+
+    public ProguardCheckDiscardRule build() {
+      return new ProguardCheckDiscardRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules);
+    }
+  }
+
+  private ProguardCheckDiscardRule(
+      ProguardTypeMatcher classAnnotation,
+      DexAccessFlags classAccessFlags,
+      DexAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      List<ProguardTypeMatcher> classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      Set<ProguardMemberRule> memberRules) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "checkdiscard";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 17e5128..31fb77d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -137,16 +137,16 @@
       } else if (acceptString("keepattributes")) {
         parseKeepAttributes();
       } else if (acceptString("keeppackagenames")) {
-        ProguardKeepRule rule = parseKeepPackageNamesRule();
+        ProguardKeepPackageNamesRule rule = parseKeepPackageNamesRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("checkdiscard")) {
-        ProguardKeepRule rule = parseCheckDiscardRule();
+        ProguardCheckDiscardRule rule = parseCheckDiscardRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("keep")) {
         ProguardKeepRule rule = parseKeepRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("whyareyoukeeping")) {
-        ProguardKeepRule rule = parseWhyAreYouKeepingRule();
+        ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("dontoptimize")) {
         configurationBuilder.setOptimize(false);
@@ -351,37 +351,36 @@
       ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
       parseRuleTypeAndModifiers(keepRuleBuilder);
       parseClassSpec(keepRuleBuilder, false);
+      if (keepRuleBuilder.getMemberRules().isEmpty()) {
+        // If there are no member rules, a default rule for the parameterless constructor
+        // applies. So we add that here.
+        ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
+        defaultRuleBuilder.setName(Constants.INSTANCE_INITIALIZER_NAME);
+        defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
+        defaultRuleBuilder.setArguments(Collections.emptyList());
+        keepRuleBuilder.getMemberRules().add(defaultRuleBuilder.build());
+      }
       return keepRuleBuilder.build();
     }
 
-    private ProguardKeepRule parseWhyAreYouKeepingRule()
+    private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule()
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
-      keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
-      keepRuleBuilder.getModifiersBuilder().whyAreYouKeeping = true;
-      keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+      ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder();
       parseClassSpec(keepRuleBuilder, false);
       return keepRuleBuilder.build();
     }
 
-    private ProguardKeepRule parseKeepPackageNamesRule()
+    private ProguardKeepPackageNamesRule parseKeepPackageNamesRule()
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
-      keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
-      keepRuleBuilder.getModifiersBuilder().keepPackageNames = true;
-      keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+      ProguardKeepPackageNamesRule.Builder keepRuleBuilder = ProguardKeepPackageNamesRule.builder();
       keepRuleBuilder.setClassNames(parseClassNames());
       return keepRuleBuilder.build();
     }
 
-    private ProguardKeepRule parseCheckDiscardRule()
+    private ProguardCheckDiscardRule parseCheckDiscardRule()
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
-      keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
-      keepRuleBuilder.getModifiersBuilder().checkDiscarded = true;
+      ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder();
       parseClassSpec(keepRuleBuilder, false);
-      keepRuleBuilder.setType(keepRuleBuilder.getMemberRules().isEmpty() ? ProguardKeepRuleType.KEEP
-          : ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
       return keepRuleBuilder.build();
     }
 
@@ -528,14 +527,6 @@
         }
         skipWhitespace();
         expectChar('}');
-      } else {
-        // If there are no member rules, a default rule for the parameterless constructor
-        // applies. So we add that here.
-        ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
-        defaultRuleBuilder.setName(Constants.INSTANCE_INITIALIZER_NAME);
-        defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
-        defaultRuleBuilder.setArguments(Collections.emptyList());
-        classSpecificationBuilder.getMemberRules().add(defaultRuleBuilder.build());
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
new file mode 100644
index 0000000..787a31c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardKeepPackageNamesRule extends ProguardConfigurationRule {
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    private Builder() {
+    }
+
+    public ProguardKeepPackageNamesRule build() {
+      return new ProguardKeepPackageNamesRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules);
+    }
+  }
+
+  private ProguardKeepPackageNamesRule(
+      ProguardTypeMatcher classAnnotation,
+      DexAccessFlags classAccessFlags,
+      DexAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      List<ProguardTypeMatcher> classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      Set<ProguardMemberRule> memberRules) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+  }
+
+  public static ProguardKeepPackageNamesRule.Builder builder() {
+    return new ProguardKeepPackageNamesRule.Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "keeppackagenames";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index e2f53b5..5db35cd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -8,51 +8,30 @@
     public boolean allowsShrinking = false;
     public boolean allowsOptimization = false;
     public boolean allowsObfuscation = false;
-    public boolean whyAreYouKeeping = false;
     public boolean includeDescriptorClasses = false;
-    public boolean keepPackageNames = false;
-    public boolean checkDiscarded = false;
-
-    void setFlagsToHaveNoEffect() {
-      allowsShrinking = true;
-      allowsOptimization = true;
-      allowsObfuscation = true;
-      whyAreYouKeeping = false;
-      includeDescriptorClasses = false;
-      keepPackageNames = false;
-    }
 
     private Builder() {}
 
     ProguardKeepRuleModifiers build() {
       return new ProguardKeepRuleModifiers(allowsShrinking, allowsOptimization, allowsObfuscation,
-          whyAreYouKeeping, includeDescriptorClasses, keepPackageNames, checkDiscarded);
+          includeDescriptorClasses);
     }
   }
 
   public final boolean allowsShrinking;
   public final boolean allowsOptimization;
   public final boolean allowsObfuscation;
-  public final boolean whyAreYouKeeping;
   public final boolean includeDescriptorClasses;
-  public final boolean keepPackageNames;
-  public final boolean checkDiscarded;
 
   private ProguardKeepRuleModifiers(
       boolean allowsShrinking,
       boolean allowsOptimization,
       boolean allowsObfuscation,
-      boolean whyAreYouKeeping,
-      boolean includeDescriptorClasses,
-      boolean keepPackageNames,
-      boolean checkDiscarded) {
+      boolean includeDescriptorClasses) {
     this.allowsShrinking = allowsShrinking;
     this.allowsOptimization = allowsOptimization;
     this.allowsObfuscation = allowsObfuscation;
-    this.whyAreYouKeeping = whyAreYouKeeping;
     this.includeDescriptorClasses = includeDescriptorClasses;
-    this.keepPackageNames = keepPackageNames;
-    this.checkDiscarded = checkDiscarded;
   }
   /**
    * Create a new empty builder.
@@ -71,8 +50,7 @@
     return allowsShrinking == that.allowsShrinking
         && allowsOptimization == that.allowsOptimization
         && allowsObfuscation == that.allowsObfuscation
-        && includeDescriptorClasses == that.includeDescriptorClasses
-        && keepPackageNames == that.keepPackageNames;
+        && includeDescriptorClasses == that.includeDescriptorClasses;
   }
 
   @Override
@@ -80,9 +58,7 @@
     return (allowsShrinking ? 1 : 0)
         | (allowsOptimization ? 2 : 0)
         | (allowsObfuscation ? 4 : 0)
-        | (whyAreYouKeeping ? 8 : 0)
-        | (includeDescriptorClasses ? 16 : 0)
-        | (keepPackageNames ? 32 : 0);
+        | (includeDescriptorClasses ? 8 : 0);
   }
 
   @Override
@@ -91,9 +67,7 @@
     appendWithComma(builder, allowsObfuscation, "allowobfuscation");
     appendWithComma(builder, allowsShrinking, "allowshrinking");
     appendWithComma(builder, allowsOptimization, "allowoptimization");
-    appendWithComma(builder, whyAreYouKeeping, "whyareyoukeeping");
     appendWithComma(builder, includeDescriptorClasses, "includedescriptorclasses");
-    appendWithComma(builder, keepPackageNames, "keeppackagenames");
     return builder.toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
new file mode 100644
index 0000000..581a0db
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    private Builder() {
+    }
+
+    public ProguardWhyAreYouKeepingRule build() {
+      return new ProguardWhyAreYouKeepingRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules);
+    }
+  }
+
+  private ProguardWhyAreYouKeepingRule(
+      ProguardTypeMatcher classAnnotation,
+      DexAccessFlags classAccessFlags,
+      DexAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      List<ProguardTypeMatcher> classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      Set<ProguardMemberRule> memberRules) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+  }
+
+  public static ProguardWhyAreYouKeepingRule.Builder builder() {
+    return new ProguardWhyAreYouKeepingRule.Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "whyareyoukeeping";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 6ac5694..1ddd7d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,9 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Sets;
-
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -24,9 +21,11 @@
 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.ThreadUtils;
-
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -185,12 +184,23 @@
             }
             case KEEP: {
               markClass(clazz, rule);
-              markClass(clazz, rule);
               markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
               markMatchingFields(clazz, memberKeepRules, rule, null);
               break;
             }
           }
+        } else if (rule instanceof ProguardCheckDiscardRule) {
+          if (memberKeepRules.isEmpty()) {
+            markClass(clazz, rule);
+          } else {
+            markMatchingFields(clazz, memberKeepRules, rule, clazz.type);
+            markMatchingMethods(clazz, memberKeepRules, rule, clazz.type);
+          }
+        } else if (rule instanceof ProguardWhyAreYouKeepingRule
+            || rule instanceof ProguardKeepPackageNamesRule) {
+          markClass(clazz, rule);
+          markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
+          markMatchingFields(clazz, memberKeepRules, rule, null);
         } else if (rule instanceof ProguardAssumeNoSideEffectRule) {
           markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
           markMatchingFields(clazz, memberKeepRules, rule, null);
@@ -247,13 +257,24 @@
       Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
       DexType onlyIfClassKept) {
     Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>();
+    Arrays.stream(clazz.directMethods()).forEach(method ->
+        markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
     while (clazz != null) {
-      clazz.forEachMethod(method ->
+      Arrays.stream(clazz.virtualMethods()).forEach(method ->
           markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
       clazz = application.definitionFor(clazz.superType);
     }
   }
 
+  private void markMatchingMethods(DexClass clazz,
+      Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+      DexType onlyIfClassKept) {
+    Arrays.stream(clazz.directMethods()).forEach(method ->
+        markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+    Arrays.stream(clazz.virtualMethods()).forEach(method ->
+        markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+  }
+
   private void markMatchingFields(DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
       DexType onlyIfClassKept) {
@@ -375,7 +396,8 @@
   private void markMethod(DexEncodedMethod method, Collection<ProguardMemberRule> rules,
       ProguardConfigurationRule context, Set<Wrapper<DexMethod>> methodsMarked,
       DexType onlyIfClassKept) {
-    if (methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
+    if ((methodsMarked != null)
+        && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
       return;
     }
     for (ProguardMemberRule rule : rules) {
@@ -384,6 +406,9 @@
           Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
               rule);
         }
+        if (methodsMarked != null) {
+          methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
+        }
         addItemToSets(method, context, rule, onlyIfClassKept);
       }
     }
@@ -461,24 +486,19 @@
       if (!modifiers.allowsObfuscation) {
         noObfuscation.add(item);
       }
-      if (modifiers.whyAreYouKeeping) {
-        assert onlyIfClassKept == null;
-        reasonAsked.add(item);
-      }
-      if (modifiers.keepPackageNames) {
-        assert onlyIfClassKept == null;
-        keepPackageName.add(item);
-      }
       if (modifiers.includeDescriptorClasses) {
         includeDescriptorClasses(item, keepRule);
       }
-      if (modifiers.checkDiscarded) {
-        checkDiscarded.add(item);
-      }
     } else if (context instanceof ProguardAssumeNoSideEffectRule) {
       noSideEffects.put(item, rule);
+    } else if (context instanceof ProguardWhyAreYouKeepingRule) {
+      reasonAsked.add(item);
+    } else if (context instanceof ProguardKeepPackageNamesRule) {
+      keepPackageName.add(item);
     } else if (context instanceof ProguardAssumeValuesRule) {
       assumedValues.put(item, rule);
+    } else if (context instanceof ProguardCheckDiscardRule) {
+      checkDiscarded.add(item);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index a297c10..2480417 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -245,9 +245,16 @@
   }
 
   /**
-   * True if the main dex list resource exists.
+   * True if the main dex list resources exists.
    */
   public boolean hasMainDexList() {
+    return !(mainDexListResources.isEmpty() && mainDexClasses.isEmpty());
+  }
+
+  /**
+   * True if the main dex list resources exists.
+   */
+  public boolean hasMainDexListResources() {
     return !mainDexListResources.isEmpty();
   }
 
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
similarity index 87%
rename from src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
rename to src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index c33d5b4..6e3c114 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -2,11 +2,13 @@
 // 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.bridgeremoval.bridgestoremove;
+package com.android.tools.r8.bridgeremoval;
 
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.bridgeremoval.bridgestoremove.Main;
+import com.android.tools.r8.bridgeremoval.bridgestoremove.Outer;
 import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
new file mode 100644
index 0000000..a56c0c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2017, 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.checkdiscarded;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.checkdiscarded.testclasses.Main;
+import com.android.tools.r8.checkdiscarded.testclasses.UnusedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.UsedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.WillBeGone;
+import com.android.tools.r8.checkdiscarded.testclasses.WillStay;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CheckDiscardedTest extends TestBase {
+
+  private void run(boolean obfuscate, Class annotation, boolean checkMembers, boolean shouldFail)
+      throws Exception {
+    List<Class> classes = ImmutableList.of(
+        UnusedClass.class,
+        UsedClass.class,
+        Main.class);
+    String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate)
+        + checkDiscardRule(checkMembers, annotation);
+    try {
+      compileWithR8(classes, proguardConfig, this::noInlining);
+    } catch (CompilationError e) {
+      Assert.assertTrue(shouldFail);
+      return;
+    }
+    Assert.assertFalse(shouldFail);
+  }
+
+  private void noInlining(InternalOptions options) {
+    options.inlineAccessors = false;
+  }
+
+  private String checkDiscardRule(boolean member, Class annotation) {
+    if (member) {
+      return "-checkdiscard class * { @" + annotation.getCanonicalName() + " *; }";
+    } else {
+      return "-checkdiscard @" + annotation.getCanonicalName() + " class *";
+    }
+  }
+
+  @Test
+  public void classesAreGone() throws Exception {
+    run(false, WillBeGone.class, false, false);
+    run(true, WillBeGone.class, false, false);
+  }
+
+  @Test
+  public void classesAreNotGone() throws Exception {
+    run(false, WillStay.class, false, true);
+    run(true, WillStay.class, false, true);
+  }
+
+  @Test
+  public void membersAreGone() throws Exception {
+    run(false, WillBeGone.class, true, false);
+    run(true, WillBeGone.class, true, false);
+  }
+
+  @Test
+  public void membersAreNotGone() throws Exception {
+    run(false, WillStay.class, true, true);
+    run(true, WillStay.class, true, true);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java
new file mode 100644
index 0000000..677330a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+@WillStay
+public class Main {
+
+  public static void main(String... args) {
+    UsedClass usedClass = new UsedClass();
+    System.out.println(usedClass.hello());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java
new file mode 100644
index 0000000..1b1689b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+@WillBeGone
+public class UnusedClass {
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
new file mode 100644
index 0000000..253c114
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+@WillStay
+public class UsedClass {
+
+  @WillStay
+  public String hello() {
+    return "hello";
+  }
+
+  @WillBeGone
+  public String world() {
+    return "world";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java
new file mode 100644
index 0000000..9176911
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+public @interface WillBeGone {
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java
new file mode 100644
index 0000000..e1925c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+public @interface WillStay {
+
+}
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
new file mode 100644
index 0000000..98f1071
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2017, 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.dex;
+
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.DexAccessFlags;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class JumboStringProcessing {
+
+  @Test
+  public void branching() {
+    DexItemFactory factory = new DexItemFactory();
+    DexString string = factory.createString("turn into jumbo");
+    factory.sort(NamingLens.getIdentityLens());
+    Instruction[] instructions = buildInstructions(string, false);
+    DexCode code = jumboStringProcess(factory, string, instructions);
+    Instruction[] rewrittenInstructions = code.instructions;
+    assert rewrittenInstructions[1] instanceof IfEq;
+    IfEq condition = (IfEq) rewrittenInstructions[1];
+    assert condition.getOffset() + condition.CCCC == rewrittenInstructions[3].getOffset();
+    assert rewrittenInstructions[2] instanceof Goto32;
+    Goto32 jump = (Goto32) rewrittenInstructions[2];
+    Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+    assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+  }
+
+  @Test
+  public void branching2() {
+    DexItemFactory factory = new DexItemFactory();
+    DexString string = factory.createString("turn into jumbo");
+    factory.sort(NamingLens.getIdentityLens());
+    Instruction[] instructions = buildInstructions(string, true);
+    DexCode code = jumboStringProcess(factory, string, instructions);
+    Instruction[] rewrittenInstructions = code.instructions;
+    assert rewrittenInstructions[1] instanceof IfEqz;
+    IfEqz condition = (IfEqz) rewrittenInstructions[1];
+    assert condition.getOffset() + condition.BBBB == rewrittenInstructions[3].getOffset();
+    assert rewrittenInstructions[2] instanceof Goto32;
+    Goto32 jump = (Goto32) rewrittenInstructions[2];
+    Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+    assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+  }
+
+  private Instruction[] buildInstructions(DexString string, boolean zeroCondition) {
+    List<Instruction> instructions = new ArrayList<>();
+    int offset = 0;
+    Instruction instr = new Const4(0, 0);
+    instr.setOffset(offset);
+    instructions.add(instr);
+    offset += instr.getSize();
+    int lastInstructionOffset = 15000 * 2 + 2 + offset;
+    if (zeroCondition) {
+      instr = new IfNez(0, lastInstructionOffset - offset);
+    } else {
+      instr = new IfNe(0, 0, lastInstructionOffset - offset);
+    }
+    instr.setOffset(offset);
+    instructions.add(instr);
+    offset += instr.getSize();
+    for (int i = 0; i < 15000; i++) {
+      instr = new ConstString(0, string);
+      instr.setOffset(offset);
+      instructions.add(instr);
+      offset += instr.getSize();
+    }
+    instr = new ReturnVoid();
+    instr.setOffset(offset);
+    instructions.add(instr);
+    assert instr.getOffset() == lastInstructionOffset;
+    return instructions.toArray(new Instruction[instructions.size()]);
+  }
+
+  private DexCode jumboStringProcess(
+      DexItemFactory factory, DexString string, Instruction[] instructions) {
+    DexCode code = new DexCode(
+        1,
+        0,
+        0,
+        instructions,
+        new Try[0],
+        null,
+        null,
+        null);
+    DexAccessFlags flags = new DexAccessFlags(0);
+    flags.setPublic();
+    DexEncodedMethod method = new DexEncodedMethod(null, flags, null, null, code);
+    new JumboStringRewriter(method, string, factory).rewrite();
+    return method.getCode().asDexCode();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
index d0ade31..61b624e 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
@@ -16,7 +16,6 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map.Entry;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class JumboStringTests extends JasminTestBase {
@@ -30,7 +29,6 @@
       EXTRA_STRINGS_PER_CLASSES_COUNT + MIN_STRING_COUNT / CLASSES_COUNT;
 
   @Test
-  @Ignore("b/35701208")
   public void test() throws Exception {
     JasminBuilder builder = new JasminBuilder();
     LinkedHashMap<String, MethodSignature> classes = new LinkedHashMap<>(CLASSES_COUNT);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index d5f83c9..a113003 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -512,7 +512,7 @@
         failedToFindClassInExpectedFile(outDir, clazz);
       }
     }
-    if (minimalMainDex) {
+    if (minimalMainDex && mainDex.size() > 0) {
       inspector.forAllClasses(clazz -> assertMainDexClass(clazz, mainDex));
     }
   }
diff --git a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
index 9cc7eda..1e8555b 100644
--- a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
@@ -50,7 +50,7 @@
   private void verifyEmptyCommand(D8Command command) throws IOException {
     assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
     assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
-    assertFalse(ToolHelper.getApp(command).hasMainDexList());
+    assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
     assertFalse(ToolHelper.getApp(command).hasProguardMap());
     assertFalse(ToolHelper.getApp(command).hasProguardSeeds());
     assertFalse(ToolHelper.getApp(command).hasPackageDistribution());
@@ -162,11 +162,11 @@
     Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
 
     D8Command command = parse("--main-dex-list", mainDexList1.toString());
-    assertTrue(ToolHelper.getApp(command).hasMainDexList());
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
 
     command = parse(
         "--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
-    assertTrue(ToolHelper.getApp(command).hasMainDexList());
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
   @Test
@@ -181,7 +181,7 @@
     thrown.expect(CompilationException.class);
     Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
     D8Command command = parse("--main-dex-list", mainDexList.toString(), "--file-per-class");
-    assertTrue(ToolHelper.getApp(command).hasMainDexList());
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
   @Test
@@ -189,7 +189,7 @@
     thrown.expect(CompilationException.class);
     Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
     D8Command command = parse("--main-dex-list", mainDexList.toString(), "--intermediate");
-    assertTrue(ToolHelper.getApp(command).hasMainDexList());
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
index 81ce88f..fdeb2dc 100644
--- a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
@@ -51,7 +51,7 @@
   private void verifyEmptyCommand(R8Command command) throws IOException {
     assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
     assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
-    assertFalse(ToolHelper.getApp(command).hasMainDexList());
+    assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
     assertFalse(ToolHelper.getApp(command).hasProguardMap());
     assertFalse(ToolHelper.getApp(command).hasProguardSeeds());
     assertFalse(ToolHelper.getApp(command).hasPackageDistribution());