Merge "Fix race in main-dex list building"
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/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/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);