Merge commit '3e0a120cf508a856c117d8784cd6477340071181' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5e43f29..9d43396 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -982,10 +982,10 @@
   }
 
   private static boolean verifyOriginalMethodInDebugInfo(DexCode code, DexMethod originalMethod) {
-    if (code.getDebugInfo() == null) {
+    if (code.getDebugInfo() == null || code.getDebugInfo().isPcBasedInfo()) {
       return true;
     }
-    for (DexDebugEvent event : code.getDebugInfo().events) {
+    for (DexDebugEvent event : code.getDebugInfo().asEventBasedInfo().events) {
       assert !event.isSetInlineFrame() || event.asSetInlineFrame().hasOuterPosition(originalMethod);
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index f8b10e0..47124c0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -532,6 +532,27 @@
   }
 
   public CfFrame map(java.util.function.Function<DexType, DexType> func) {
+    boolean mapped = false;
+    for (int var : locals.keySet()) {
+      CfFrame.FrameType originalType = locals.get(var);
+      CfFrame.FrameType mappedType = originalType.map(func);
+      mapped = originalType != mappedType;
+      if (mapped) {
+        break;
+      }
+    }
+    if (!mapped) {
+      for (FrameType frameType : stack) {
+        CfFrame.FrameType mappedType = frameType.map(func);
+        mapped = frameType != mappedType;
+        if (mapped) {
+          break;
+        }
+      }
+    }
+    if (!mapped) {
+      return this;
+    }
     Int2ReferenceSortedMap<FrameType> newLocals = new Int2ReferenceAVLTreeMap<>();
     for (int var : locals.keySet()) {
       newLocals.put(var, locals.get(var).map(func));
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index f99eee5..5d26e0a 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -26,7 +26,7 @@
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForWriting;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -132,7 +132,7 @@
     }
 
     @Override
-    public boolean add(DexDebugInfo dexDebugInfo) {
+    public boolean add(DexDebugInfoForWriting dexDebugInfo) {
       return true;
     }
 
@@ -488,7 +488,7 @@
     timing.end();
 
     timing.begin("Write bytes");
-    ByteBufferResult result = writeDexFile(objectMapping, byteBufferProvider);
+    ByteBufferResult result = writeDexFile(objectMapping, byteBufferProvider, timing);
     ByteDataView data =
         new ByteDataView(result.buffer.array(), result.buffer.arrayOffset(), result.length);
     timing.end();
@@ -789,8 +789,7 @@
   }
 
   private ByteBufferResult writeDexFile(
-      ObjectToOffsetMapping objectMapping,
-      ByteBufferProvider provider) {
+      ObjectToOffsetMapping objectMapping, ByteBufferProvider provider, Timing timing) {
     FileWriter fileWriter =
         new FileWriter(
             provider,
@@ -800,9 +799,9 @@
             namingLens,
             desugaredLibraryCodeToKeep);
     // Collect the non-fixed sections.
-    fileWriter.collect();
+    timing.scope("collect", () -> fileWriter.collect());
     // Generate and write the bytes.
-    return fileWriter.generate();
+    return timing.scope("generate", () -> fileWriter.generate());
   }
 
   private static String mapMainDexListName(DexType type, NamingLens namingLens) {
diff --git a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
index 2c3e18a..5a6a377 100644
--- a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
@@ -3,8 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
-import com.android.tools.r8.graph.DexDebugEvent;
-import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForWriting;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -17,32 +16,19 @@
 
   private final ObjectToOffsetMapping mapping;
   private final GraphLens graphLens;
-  private final DexDebugInfo info;
+  private final DexDebugInfoForWriting info;
   private ByteBuffer buffer;
 
   public DebugBytecodeWriter(
-      DexDebugInfo info, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      DexDebugInfoForWriting info, ObjectToOffsetMapping mapping, GraphLens graphLens) {
     this.info = info;
     this.mapping = mapping;
     this.graphLens = graphLens;
-    // Never allocate a zero-sized buffer, as we need to write the header, and the growth policy
-    // requires it to have a positive capacity.
-    this.buffer = ByteBuffer.allocate(info.events.length * 5 + 4);
+    this.buffer = ByteBuffer.allocate(info.estimatedWriteSize());
   }
 
   public byte[] generate() {
-    // Header.
-    putUleb128(info.startLine); // line_start
-    putUleb128(info.parameters.length);
-    for (DexString name : info.parameters) {
-      putString(name);
-    }
-    // Body.
-    for (DexDebugEvent event : info.events) {
-      event.writeOn(this, mapping, graphLens);
-    }
-    // Tail.
-    putByte(Constants.DBG_END_SEQUENCE);
+    info.write(this, mapping, graphLens);
     return Arrays.copyOf(buffer.array(), buffer.position());
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 43a9a57..7a3ccf5 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -25,7 +25,10 @@
 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.Default;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.PcBasedDebugInfo;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -522,22 +525,28 @@
 
   private DexDebugInfo parseDebugInfo() {
     int start = dexReader.getUleb128();
+    boolean isPcBasedDebugInfo = start == 0;
     int parametersSize = dexReader.getUleb128();
     DexString[] parameters = new DexString[parametersSize];
     for (int i = 0; i < parametersSize; i++) {
       int index = dexReader.getUleb128p1();
       if (index != NO_INDEX) {
         parameters[i] = indexedItems.getString(index);
+        isPcBasedDebugInfo = false;
       }
     }
     List<DexDebugEvent> events = new ArrayList<>();
-    for (int head = dexReader.getUbyte(); head != Constants.DBG_END_SEQUENCE; head = dexReader.getUbyte()) {
+    for (int head = dexReader.getUbyte();
+        head != Constants.DBG_END_SEQUENCE;
+        head = dexReader.getUbyte()) {
       switch (head) {
         case Constants.DBG_ADVANCE_PC:
           events.add(dexItemFactory.createAdvancePC(dexReader.getUleb128()));
+          isPcBasedDebugInfo = false;
           break;
         case Constants.DBG_ADVANCE_LINE:
           events.add(dexItemFactory.createAdvanceLine(dexReader.getSleb128()));
+          isPcBasedDebugInfo = false;
           break;
         case Constants.DBG_START_LOCAL: {
           int registerNum = dexReader.getUleb128();
@@ -548,6 +557,7 @@
               nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx),
               typeIdx == NO_INDEX ? null : indexedItems.getType(typeIdx),
               null));
+          isPcBasedDebugInfo = false;
           break;
         }
         case Constants.DBG_START_LOCAL_EXTENDED: {
@@ -560,22 +570,27 @@
               nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx),
               typeIdx == NO_INDEX ? null : indexedItems.getType(typeIdx),
               sigIdx == NO_INDEX ? null : indexedItems.getString(sigIdx)));
+          isPcBasedDebugInfo = false;
           break;
         }
         case Constants.DBG_END_LOCAL: {
           events.add(dexItemFactory.createEndLocal(dexReader.getUleb128()));
+          isPcBasedDebugInfo = false;
           break;
         }
         case Constants.DBG_RESTART_LOCAL: {
           events.add(dexItemFactory.createRestartLocal(dexReader.getUleb128()));
+          isPcBasedDebugInfo = false;
           break;
         }
         case Constants.DBG_SET_PROLOGUE_END: {
           events.add(dexItemFactory.createSetPrologueEnd());
+          isPcBasedDebugInfo = false;
           break;
         }
         case Constants.DBG_SET_EPILOGUE_BEGIN: {
           events.add(dexItemFactory.createSetEpilogueBegin());
+          isPcBasedDebugInfo = false;
           break;
         }
         case Constants.DBG_SET_FILE: {
@@ -584,15 +599,26 @@
           if (options.readDebugSetFileEvent) {
             events.add(dexItemFactory.createSetFile(sourceFile));
           }
+          isPcBasedDebugInfo = false;
           break;
         }
         default: {
           assert head >= 0x0a && head <= 0xff;
-          events.add(dexItemFactory.createDefault(head));
+          Default event = dexItemFactory.createDefault(head);
+          events.add(event);
+          if (isPcBasedDebugInfo) {
+            if (events.size() == 1) {
+              isPcBasedDebugInfo = event.equals(dexItemFactory.zeroChangeDefaultEvent);
+            } else {
+              isPcBasedDebugInfo = event.equals(dexItemFactory.oneChangeDefaultEvent);
+            }
+          }
         }
       }
     }
-    return new DexDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY));
+    return isPcBasedDebugInfo
+        ? new PcBasedDebugInfo(parametersSize, events.size() - 1)
+        : new EventBasedDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY));
   }
 
   private static class MemberAnnotationIterator<R extends DexMember<?, R>, T extends DexItem> {
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 84b245c..ebf21c6 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -22,7 +22,6 @@
 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.DexDebugInfo;
 import com.android.tools.r8.graph.DexDebugInfoForWriting;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedArray;
@@ -184,7 +183,7 @@
     } else {
       // Ensure deterministic ordering of debug info by sorting consistent with the code objects.
       layout.setDebugInfosOffset(dest.align(1));
-      Set<DexDebugInfo> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size());
+      Set<DexDebugInfoForWriting> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size());
       for (ProgramDexCode code : codes) {
         DexDebugInfoForWriting info = code.getCode().getDebugInfoForWriting();
         if (info != null && seen.add(info)) {
@@ -480,7 +479,7 @@
     dest.putInt(mixedSectionOffsets.getOffsetFor(staticFieldValues.get(clazz)));
   }
 
-  private void writeDebugItem(DexDebugInfo debugInfo, GraphLens graphLens) {
+  private void writeDebugItem(DexDebugInfoForWriting debugInfo, GraphLens graphLens) {
     mixedSectionOffsets.setOffsetFor(debugInfo, dest.position());
     dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping, graphLens).generate());
   }
@@ -1070,7 +1069,7 @@
     private static final int NOT_KNOWN = -2;
 
     private final Reference2IntMap<DexEncodedMethod> codes = createReference2IntMap();
-    private final Object2IntMap<DexDebugInfo> debugInfos = createObject2IntMap();
+    private final Object2IntMap<DexDebugInfoForWriting> debugInfos = createObject2IntMap();
     private final Object2IntMap<DexTypeList> typeLists = createObject2IntMap();
     private final Reference2IntMap<DexString> stringData = createReference2IntMap();
     private final Object2IntMap<DexAnnotation> annotations = createObject2IntMap();
@@ -1149,7 +1148,7 @@
     }
 
     @Override
-    public boolean add(DexDebugInfo debugInfo) {
+    public boolean add(DexDebugInfoForWriting debugInfo) {
       return add(debugInfos, debugInfo);
     }
 
@@ -1190,7 +1189,7 @@
       return codes.keySet();
     }
 
-    public Collection<DexDebugInfo> getDebugInfos() {
+    public Collection<DexDebugInfoForWriting> getDebugInfos() {
       return debugInfos.keySet();
     }
 
@@ -1263,7 +1262,7 @@
       return lookup(encodedArray, encodedArrays);
     }
 
-    public int getOffsetFor(DexDebugInfo debugInfo) {
+    public int getOffsetFor(DexDebugInfoForWriting debugInfo) {
       return lookup(debugInfo, debugInfos);
     }
 
@@ -1311,7 +1310,7 @@
       assert old <= NOT_SET;
     }
 
-    void setOffsetFor(DexDebugInfo debugInfo, int offset) {
+    void setOffsetFor(DexDebugInfoForWriting debugInfo, int offset) {
       setOffsetFor(debugInfo, offset, debugInfos);
     }
 
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index 990d545..fc3784f 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -37,6 +37,7 @@
 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.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
@@ -93,8 +94,8 @@
   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 EventBasedDebugInfo debugEventBasedInfo = null;
+  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 Int2ReferenceMap<Instruction> tryRangeStartAndEndTargets
@@ -216,11 +217,12 @@
 
   private DexDebugInfo rewriteDebugInfoOffsets() {
     DexCode code = method.getCode().asDexCode();
-    if (debugEventTargets.size() != 0) {
+    if (!debugEventTargets.isEmpty()) {
+      assert debugEventBasedInfo != null;
       int lastOriginalOffset = 0;
       int lastNewOffset = 0;
       List<DexDebugEvent> events = new ArrayList<>();
-      for (DexDebugEvent event : code.getDebugInfo().events) {
+      for (DexDebugEvent event : debugEventBasedInfo.events) {
         if (event instanceof AdvancePC) {
           AdvancePC advance = (AdvancePC) event;
           lastOriginalOffset += advance.delta;
@@ -240,9 +242,9 @@
           events.add(event);
         }
       }
-      return new DexDebugInfo(
-          code.getDebugInfo().startLine,
-          code.getDebugInfo().parameters,
+      return new EventBasedDebugInfo(
+          debugEventBasedInfo.startLine,
+          debugEventBasedInfo.parameters,
           events.toArray(DexDebugEvent.EMPTY_ARRAY));
     }
     return code.getDebugInfo();
@@ -479,23 +481,30 @@
   }
 
   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);
-        }
+    // TODO(b/213411850): Merging pc based D8 builds will map out of PC for any jumbo processed
+    //  method. Instead we should rather retain the PC encoding by bumping the max-pc and recording
+    //  the line number translation. We actually need to do so to support merging with native PC
+    //  support as in that case we can't reflect the change in the line table, only in the mapping.
+    EventBasedDebugInfo eventBasedInfo =
+        DexDebugInfo.convertToEventBased(method.getCode().asDexCode(), factory);
+    if (eventBasedInfo == null) {
+      return;
+    }
+    debugEventBasedInfo = eventBasedInfo;
+    int address = 0;
+    for (DexDebugEvent event : eventBasedInfo.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);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index e082a31..f0c4c06 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForWriting;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
@@ -74,11 +74,11 @@
   /**
    * Adds the given debug info to the collection.
    *
-   * Does not add any dependencies.
+   * <p>Does not add any dependencies.
    *
    * @return true if the item was not added before
    */
-  public abstract boolean add(DexDebugInfo dexDebugInfo);
+  public abstract boolean add(DexDebugInfoForWriting dexDebugInfo);
 
   /**
    * Adds the given type list to the collection.
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 7c7824d..07ebb29 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -803,7 +803,7 @@
   }
 
   @Override
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
     Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2);
     CfLabel firstLabel;
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index be9507d..e727373 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -149,7 +149,7 @@
     return true;
   }
 
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
     throw new Unreachable();
   }
 }
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 703977e..11d3e17 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
 import com.android.tools.r8.ir.code.IRCode;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.Equatable;
 import com.android.tools.r8.utils.structural.HashCodeVisitor;
@@ -170,7 +172,7 @@
     }
   }
 
-  public DexCode withoutThisParameter() {
+  public DexCode withoutThisParameter(DexItemFactory factory) {
     // Note that we assume the original code has a register associated with 'this'
     // argument of the (former) instance method. We also assume (but do not check)
     // that 'this' register is never used, so when we decrease incoming register size
@@ -184,7 +186,7 @@
         instructions,
         tries,
         handlers,
-        debugInfoWithoutFirstParameter());
+        debugInfoWithoutFirstParameter(factory));
   }
 
   @Override
@@ -229,16 +231,17 @@
   }
 
   public DexDebugInfo debugInfoWithFakeThisParameter(DexItemFactory factory) {
-    if (debugInfo == null) {
-      return null;
+    EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
+    if (eventBasedInfo == null) {
+      return eventBasedInfo;
     }
     // User code may already have variables named '_*this'. Use one more than the largest number of
     // underscores present as a prefix to 'this'.
     int largestPrefix = 0;
-    for (DexString parameter : debugInfo.parameters) {
+    for (DexString parameter : eventBasedInfo.parameters) {
       largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, parameter));
     }
-    for (DexDebugEvent event : debugInfo.events) {
+    for (DexDebugEvent event : eventBasedInfo.events) {
       if (event instanceof DexDebugEvent.StartLocal) {
         DexString name = ((StartLocal) event).name;
         largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, name));
@@ -246,15 +249,15 @@
     }
 
     String fakeThisName = Strings.repeat(FAKE_THIS_PREFIX, largestPrefix + 1) + FAKE_THIS_SUFFIX;
-    DexString[] parameters = debugInfo.parameters;
+    DexString[] parameters = eventBasedInfo.parameters;
     DexString[] newParameters = new DexString[parameters.length + 1];
     newParameters[0] = factory.createString(fakeThisName);
     System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
-    return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
+    return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events);
   }
 
   @Override
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
     return new DexCode(
         registerSize,
         incomingRegisterSize,
@@ -262,24 +265,25 @@
         instructions,
         tries,
         handlers,
-        debugInfoAsInlining(caller, callee));
+        debugInfoAsInlining(caller, callee, factory));
   }
 
-  private DexDebugInfo debugInfoAsInlining(DexMethod caller, DexMethod callee) {
+  private DexDebugInfo debugInfoAsInlining(
+      DexMethod caller, DexMethod callee, DexItemFactory factory) {
     Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
-    if (debugInfo == null) {
+    EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
+    if (eventBasedInfo == null) {
       // If the method has no debug info we generate a preamble position to denote the inlining.
       // This is consistent with the building IR for inlining which will always ensure the method
       // has a position.
-      return new DexDebugInfo(
+      return new EventBasedDebugInfo(
           0,
           new DexString[callee.getArity()],
           new DexDebugEvent[] {
-            new DexDebugEvent.SetInlineFrame(callee, callerPosition),
-            DexDebugEvent.ZERO_CHANGE_DEFAULT_EVENT
+            new DexDebugEvent.SetInlineFrame(callee, callerPosition), factory.zeroChangeDefaultEvent
           });
     }
-    DexDebugEvent[] oldEvents = debugInfo.events;
+    DexDebugEvent[] oldEvents = eventBasedInfo.events;
     DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1];
     int i = 0;
     newEvents[i++] = new DexDebugEvent.SetInlineFrame(callee, callerPosition);
@@ -296,7 +300,7 @@
         newEvents[i++] = event;
       }
     }
-    return new DexDebugInfo(debugInfo.startLine, debugInfo.parameters, newEvents);
+    return new EventBasedDebugInfo(eventBasedInfo.startLine, eventBasedInfo.parameters, newEvents);
   }
 
   public static int getLargestPrefix(DexItemFactory factory, DexString name) {
@@ -311,17 +315,18 @@
     return 0;
   }
 
-  public DexDebugInfo debugInfoWithoutFirstParameter() {
-    if (debugInfo == null) {
-      return null;
+  public DexDebugInfo debugInfoWithoutFirstParameter(DexItemFactory factory) {
+    EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
+    if (eventBasedInfo == null) {
+      return eventBasedInfo;
     }
-    DexString[] parameters = debugInfo.parameters;
+    DexString[] parameters = eventBasedInfo.parameters;
     if(parameters.length == 0) {
-      return debugInfo;
+      return eventBasedInfo;
     }
     DexString[] newParameters = new DexString[parameters.length - 1];
     System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1);
-    return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
+    return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events);
   }
 
   @Override
@@ -362,7 +367,8 @@
             this,
             method,
             appView.graphLens().getOriginalMethodSignature(method.getReference()),
-            null);
+            null,
+            appView.dexItemFactory());
     return IRBuilder.create(method, appView, source, origin).build(method);
   }
 
@@ -381,7 +387,8 @@
             this,
             method,
             appView.graphLens().getOriginalMethodSignature(method.getReference()),
-            callerPosition);
+            callerPosition,
+            appView.dexItemFactory());
     return IRBuilder.createForInlining(
             method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
         .build(context);
@@ -443,13 +450,15 @@
 
     DexDebugEntry debugInfo = null;
     Iterator<DexDebugEntry> debugInfoIterator = Collections.emptyIterator();
-    if (getDebugInfo() != null && method != null) {
+    boolean isPcBasedInfo = getDebugInfo() != null && getDebugInfo().isPcBasedInfo();
+    if (!isPcBasedInfo && getDebugInfo() != null && method != null) {
       debugInfoIterator = new DexDebugEntryBuilder(method, new DexItemFactory()).build().iterator();
       debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
     }
     int instructionNumber = 0;
     Map<Integer, DebugLocalInfo> locals = Collections.emptyMap();
     for (Instruction insn : instructions) {
+      debugInfo = advanceToOffset(insn.getOffset() - 1, debugInfo, debugInfoIterator);
       while (debugInfo != null && debugInfo.address == insn.getOffset()) {
         if (debugInfo.lineEntry || !locals.equals(debugInfo.locals)) {
           builder.append("         ").append(debugInfo.toString(false)).append("\n");
@@ -467,8 +476,16 @@
       }
       builder.append('\n');
     }
-    if (debugInfoIterator.hasNext()) {
-      throw new Unreachable("Could not print all debug information.");
+    if (isPcBasedInfo) {
+      builder.append(getDebugInfo()).append("\n");
+    } else if (debugInfoIterator.hasNext()) {
+      Instruction lastInstruction = ArrayUtils.last(instructions);
+      debugInfo = advanceToOffset(lastInstruction.getOffset(), debugInfo, debugInfoIterator);
+      if (debugInfo != null) {
+        throw new Unreachable("Could not print all debug information.");
+      } else {
+        builder.append("(has debug events past last pc)\n");
+      }
     }
     if (tries.length > 0) {
       builder.append("Tries (numbers are offsets)\n");
@@ -488,6 +505,14 @@
     return builder.toString();
   }
 
+  DexDebugEntry advanceToOffset(
+      int offset, DexDebugEntry current, Iterator<DexDebugEntry> iterator) {
+    while (current != null && current.address <= offset) {
+      current = iterator.hasNext() ? iterator.next() : null;
+    }
+    return current;
+  }
+
   public String toSmaliString(ClassNameMapper naming) {
     StringBuilder builder = new StringBuilder();
     // Find labeled targets.
@@ -566,18 +591,15 @@
     if (debugInfo != null) {
       getDebugInfoForWriting().collectIndexedItems(indexedItems, graphLens);
     }
-      for (TryHandler handler : handlers) {
-        handler.collectIndexedItems(indexedItems, graphLens);
-      }
+    for (TryHandler handler : handlers) {
+      handler.collectIndexedItems(indexedItems, graphLens);
+    }
   }
 
   @Override
   public DexDebugInfoForWriting getDebugInfoForWriting() {
-    if (debugInfo == null) {
-      return null;
-    }
     if (debugInfoForWriting == null) {
-      debugInfoForWriting = DexDebugInfoForWriting.create(debugInfo);
+      debugInfoForWriting = DexDebugInfo.convertToWritable(debugInfo);
     }
     return debugInfoForWriting;
   }
@@ -619,10 +641,6 @@
     }
   }
 
-  public boolean usesExceptionHandling() {
-    return tries.length != 0;
-  }
-
   @Override
   public void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     if (debugInfo != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index ce338d6..d8b6a9b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.ir.code.ValueType;
 import com.google.common.collect.ImmutableMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
@@ -67,11 +68,11 @@
   public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) {
     assert method != null && method.getReference() != null;
     this.method = method.getReference();
-    positionState =
-        new DexDebugPositionState(
-            method.getCode().asDexCode().getDebugInfo().startLine, method.getReference());
     DexCode code = method.getCode().asDexCode();
-    DexDebugInfo info = code.getDebugInfo();
+    EventBasedDebugInfo info = code.getDebugInfo().asEventBasedInfo();
+    // Only event based debug info supports conversion to entries.
+    assert info != null;
+    positionState = new DexDebugPositionState(info.startLine, method.getReference());
     int argumentRegister = code.registerSize - code.incomingRegisterSize;
     if (!method.accessFlags.isStatic()) {
       DexString name = factory.thisName;
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 e4994c0..60cc65a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -26,8 +26,6 @@
 
   public static final DexDebugEvent[] EMPTY_ARRAY = {};
 
-  public static final DexDebugEvent.Default ZERO_CHANGE_DEFAULT_EVENT = Default.create(0, 0);
-
   public void collectIndexedItems(IndexedItemCollection collection, GraphLens graphLens) {
     // Empty by default.
   }
@@ -746,12 +744,14 @@
       this.value = value;
     }
 
-    public static int computeSpecialOpcode(int lineDelta, int pcDelta) {
+    // Use DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary instead.
+    static int computeSpecialOpcode(int lineDelta, int pcDelta) {
       return Constants.DBG_FIRST_SPECIAL
           + (lineDelta - Constants.DBG_LINE_BASE)
           + Constants.DBG_LINE_RANGE * pcDelta;
     }
 
+    // Use DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary instead.
     public static Default create(int lineDelta, int pcDelta) {
       return new Default(computeSpecialOpcode(lineDelta, pcDelta));
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 71822f6..dc90361 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.IRCode;
@@ -120,7 +121,7 @@
         params[i] = (local == null || local.signature != null) ? null : local.name;
       }
     }
-    return new DexDebugInfo(startLine, params, events.toArray(DexDebugEvent.EMPTY_ARRAY));
+    return new EventBasedDebugInfo(startLine, params, events.toArray(DexDebugEvent.EMPTY_ARRAY));
   }
 
   private void updateBlockEntry(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 75588f6..0f27bda 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -3,92 +3,368 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.DebugBytecodeWriter;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.LebUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.Equatable;
+import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
-public class DexDebugInfo extends CachedHashValueDexItem implements StructuralItem<DexDebugInfo> {
+public abstract class DexDebugInfo extends CachedHashValueDexItem
+    implements StructuralItem<DexDebugInfo> {
 
-  public final int startLine;
-  public final DexString[] parameters;
-  public DexDebugEvent[] events;
-
-  private static void specify(StructuralSpecification<DexDebugInfo, ?> spec) {
-    spec.withInt(d -> d.startLine)
-        .withItemArrayAllowingNullMembers(d -> d.parameters)
-        .withItemArray(d -> d.events);
+  private enum DebugInfoKind {
+    EVENT_BASED,
+    PC_BASED
   }
 
-  public DexDebugInfo(int startLine, DexString[] parameters, DexDebugEvent[] events) {
-    assert startLine >= 0;
-    this.startLine = startLine;
-    this.parameters = parameters;
-    this.events = events;
-    // This call to hashCode is just an optimization to speedup equality when
-    // canonicalizing DexDebugInfo objects inside a synchronize method.
-    hashCode();
+  abstract DebugInfoKind getKind();
+
+  abstract int internalAcceptCompareTo(DexDebugInfo other, CompareToVisitor visitor);
+
+  public abstract int getStartLine();
+
+  public abstract int getParameterCount();
+
+  public boolean isEventBasedInfo() {
+    return getKind() == DebugInfoKind.EVENT_BASED;
+  }
+
+  public boolean isPcBasedInfo() {
+    return getKind() == DebugInfoKind.PC_BASED;
+  }
+
+  public EventBasedDebugInfo asEventBasedInfo() {
+    return null;
+  }
+
+  public PcBasedDebugInfo asPcBasedInfo() {
+    return null;
   }
 
   @Override
-  public DexDebugInfo self() {
-    return this;
-  }
+  abstract void collectMixedSectionItems(MixedSectionCollection collection);
+
+  @Override
+  public abstract DexDebugInfo self();
 
   @Override
   public StructuralMapping<DexDebugInfo> getStructuralMapping() {
-    return DexDebugInfo::specify;
+    throw new Unreachable();
   }
 
-  public List<DexDebugEntry> computeEntries(DexMethod method) {
-    DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine, method);
-    for (DexDebugEvent event : events) {
-      event.accept(builder);
+  @Override
+  public abstract void acceptHashing(HashingVisitor visitor);
+
+  @Override
+  public int acceptCompareTo(DexDebugInfo other, CompareToVisitor visitor) {
+    int diff = visitor.visitInt(getKind().ordinal(), other.getKind().ordinal());
+    if (diff != 0) {
+      return diff;
     }
-    return builder.build();
+    return internalAcceptCompareTo(other, visitor);
   }
 
   @Override
-  public int computeHashCode() {
-    return startLine
-        + Arrays.hashCode(parameters) * 7
-        + Arrays.hashCode(events) * 13;
-  }
-
-  @Override
-  public final boolean computeEquals(Object other) {
+  protected final boolean computeEquals(Object other) {
     return Equatable.equalsImpl(this, other);
   }
 
-  public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
-    for (DexString parameter : parameters) {
-      if (parameter != null) {
-        parameter.collectIndexedItems(indexedItems);
+  public static class PcBasedDebugInfo extends DexDebugInfo implements DexDebugInfoForWriting {
+    private static final int START_LINE = 0;
+    private final int parameterCount;
+    private final int maxPc;
+
+    private static void specify(StructuralSpecification<PcBasedDebugInfo, ?> spec) {
+      spec.withInt(d -> d.parameterCount).withInt(d -> d.maxPc);
+    }
+
+    public PcBasedDebugInfo(int parameterCount, int maxPc) {
+      this.parameterCount = parameterCount;
+      this.maxPc = maxPc;
+    }
+
+    @Override
+    public int getStartLine() {
+      return START_LINE;
+    }
+
+    @Override
+    public int getParameterCount() {
+      return parameterCount;
+    }
+
+    @Override
+    public DexDebugInfo self() {
+      return this;
+    }
+
+    @Override
+    public PcBasedDebugInfo asPcBasedInfo() {
+      return this;
+    }
+
+    @Override
+    DebugInfoKind getKind() {
+      return DebugInfoKind.PC_BASED;
+    }
+
+    @Override
+    protected int computeHashCode() {
+      return Objects.hash(parameterCount, maxPc);
+    }
+
+    @Override
+    public void acceptHashing(HashingVisitor visitor) {
+      visitor.visit(this, PcBasedDebugInfo::specify);
+    }
+
+    @Override
+    int internalAcceptCompareTo(DexDebugInfo other, CompareToVisitor visitor) {
+      assert other.isPcBasedInfo();
+      return visitor.visit(this, other.asPcBasedInfo(), PcBasedDebugInfo::specify);
+    }
+
+    @Override
+    public void collectMixedSectionItems(MixedSectionCollection collection) {
+      collection.add(this);
+    }
+
+    @Override
+    public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
+      // No indexed items to collect.
+    }
+
+    @Override
+    public int estimatedWriteSize() {
+      return LebUtils.sizeAsUleb128(START_LINE)
+          + LebUtils.sizeAsUleb128(parameterCount)
+          + parameterCount * LebUtils.sizeAsUleb128(0)
+          + 1
+          + maxPc
+          + 1;
+    }
+
+    @Override
+    public void write(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      writer.putUleb128(START_LINE);
+      writer.putUleb128(parameterCount);
+      for (int i = 0; i < parameterCount; i++) {
+        writer.putString(null);
+      }
+      mapping.dexItemFactory().zeroChangeDefaultEvent.writeOn(writer, mapping, graphLens);
+      for (int i = 0; i < maxPc; i++) {
+        mapping.dexItemFactory().oneChangeDefaultEvent.writeOn(writer, mapping, graphLens);
+      }
+      writer.putByte(Constants.DBG_END_SEQUENCE);
+    }
+
+    @Override
+    public String toString() {
+      return "PcBasedDebugInfo (params: "
+          + parameterCount
+          + ", max-pc: "
+          + StringUtils.hexString(maxPc, 2)
+          + ")";
+    }
+  }
+
+  public static class EventBasedDebugInfo extends DexDebugInfo {
+
+    public final int startLine;
+    public final DexString[] parameters;
+    public DexDebugEvent[] events;
+
+    private static void specify(StructuralSpecification<EventBasedDebugInfo, ?> spec) {
+      spec.withInt(d -> d.startLine)
+          .withItemArrayAllowingNullMembers(d -> d.parameters)
+          .withItemArray(d -> d.events);
+    }
+
+    public EventBasedDebugInfo(int startLine, DexString[] parameters, DexDebugEvent[] events) {
+      assert startLine >= 0;
+      this.startLine = startLine;
+      this.parameters = parameters;
+      this.events = events;
+    }
+
+    @Override
+    public DexDebugInfo self() {
+      return this;
+    }
+
+    @Override
+    public EventBasedDebugInfo asEventBasedInfo() {
+      return this;
+    }
+
+    @Override
+    DebugInfoKind getKind() {
+      return DebugInfoKind.EVENT_BASED;
+    }
+
+    @Override
+    public int getStartLine() {
+      return startLine;
+    }
+
+    @Override
+    public int getParameterCount() {
+      return parameters.length;
+    }
+
+    public List<DexDebugEntry> computeEntries(DexMethod method) {
+      DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine, method);
+      for (DexDebugEvent event : events) {
+        event.accept(builder);
+      }
+      return builder.build();
+    }
+
+    @Override
+    public int computeHashCode() {
+      return startLine + Arrays.hashCode(parameters) * 7 + Arrays.hashCode(events) * 13;
+    }
+
+    @Override
+    public void acceptHashing(HashingVisitor visitor) {
+      visitor.visit(this, EventBasedDebugInfo::specify);
+    }
+
+    @Override
+    int internalAcceptCompareTo(DexDebugInfo other, CompareToVisitor visitor) {
+      assert other.isEventBasedInfo();
+      return visitor.visit(this, other.asEventBasedInfo(), EventBasedDebugInfo::specify);
+    }
+
+    public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
+      for (DexString parameter : parameters) {
+        if (parameter != null) {
+          parameter.collectIndexedItems(indexedItems);
+        }
+      }
+      for (DexDebugEvent event : events) {
+        event.collectIndexedItems(indexedItems, graphLens);
       }
     }
-    for (DexDebugEvent event : events) {
-      event.collectIndexedItems(indexedItems, graphLens);
+
+    @Override
+    void collectMixedSectionItems(MixedSectionCollection collection) {
+      // Only writable info should be iterated for collection.
+      throw new Unreachable();
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append("DebugInfo (line " + startLine + ") events: [\n");
+      for (DexDebugEvent event : events) {
+        builder.append("  ").append(event).append("\n");
+      }
+      builder.append("  END_SEQUENCE\n");
+      builder.append("]\n");
+      return builder.toString();
     }
   }
 
-  @Override
-  void collectMixedSectionItems(MixedSectionCollection collection) {
-    collection.add(this);
+  public static EventBasedDebugInfo convertToEventBased(DexCode code, DexItemFactory factory) {
+    if (code.getDebugInfo() == null) {
+      return null;
+    }
+    if (code.getDebugInfo().isEventBasedInfo()) {
+      return code.getDebugInfo().asEventBasedInfo();
+    }
+    assert code.getDebugInfo().isPcBasedInfo();
+    PcBasedDebugInfo pcBasedDebugInfo = code.getDebugInfo().asPcBasedInfo();
+    // Generate a line event at each throwing instruction.
+    List<DexDebugEvent> events = new ArrayList<>(code.instructions.length + 1);
+    events.add(factory.zeroChangeDefaultEvent);
+    int pc = 0;
+    int delta = 0;
+    for (Instruction instruction : code.instructions) {
+      delta += instruction.getSize();
+      if (instruction.canThrow()) {
+        DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary(delta, delta, events, factory);
+        pc += delta;
+        delta = 0;
+      }
+    }
+    assert pc + delta - ArrayUtils.last(code.instructions).getSize() <= pcBasedDebugInfo.maxPc;
+    return new EventBasedDebugInfo(
+        PcBasedDebugInfo.START_LINE,
+        new DexString[pcBasedDebugInfo.getParameterCount()],
+        events.toArray(DexDebugEvent.EMPTY_ARRAY));
   }
 
-  @Override
-  public String toString() {
-    StringBuilder builder = new StringBuilder();
-    builder.append("DebugInfo (line " + startLine + ") events: [\n");
-    for (DexDebugEvent event : events) {
-      builder.append("  ").append(event).append("\n");
+  public static DexDebugInfoForWriting convertToWritable(DexDebugInfo debugInfo) {
+    if (debugInfo == null) {
+      return null;
     }
-    builder.append("  END_SEQUENCE\n");
-    builder.append("]\n");
-    return builder.toString();
+    if (debugInfo.isPcBasedInfo()) {
+      return debugInfo.asPcBasedInfo();
+    }
+    EventBasedDebugInfo eventBasedInfo = debugInfo.asEventBasedInfo();
+    DexDebugEvent[] writableEvents =
+        ArrayUtils.filter(
+            eventBasedInfo.events, DexDebugEvent::isWritableEvent, DexDebugEvent.EMPTY_ARRAY);
+    return new WritableEventBasedDebugInfo(
+        eventBasedInfo.startLine, eventBasedInfo.parameters, writableEvents);
+  }
+
+  private static class WritableEventBasedDebugInfo extends EventBasedDebugInfo
+      implements DexDebugInfoForWriting {
+
+    private WritableEventBasedDebugInfo(
+        int startLine, DexString[] parameters, DexDebugEvent[] writableEvents) {
+      super(startLine, parameters, writableEvents);
+    }
+
+    @Override
+    public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
+      super.collectIndexedItems(indexedItems, graphLens);
+    }
+
+    @Override
+    public void collectMixedSectionItems(MixedSectionCollection collection) {
+      collection.add(this);
+    }
+
+    @Override
+    public int estimatedWriteSize() {
+      return LebUtils.sizeAsUleb128(startLine)
+          + LebUtils.sizeAsUleb128(parameters.length)
+          // Estimate 4 bytes per parameter pointer.
+          + parameters.length * 4
+          + events.length
+          + 1;
+    }
+
+    @Override
+    public void write(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      writer.putUleb128(startLine);
+      writer.putUleb128(parameters.length);
+      for (DexString name : parameters) {
+        writer.putString(name);
+      }
+      for (DexDebugEvent event : events) {
+        event.writeOn(writer, mapping, graphLens);
+      }
+      writer.putByte(Constants.DBG_END_SEQUENCE);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
index 32d3126..8d41561 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.graph;
 
-public class DexDebugInfoForSingleLineMethod extends DexDebugInfo {
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
+
+public class DexDebugInfoForSingleLineMethod extends EventBasedDebugInfo {
 
   private static final DexDebugInfoForSingleLineMethod INSTANCE =
       new DexDebugInfoForSingleLineMethod(0, DexString.EMPTY_ARRAY, DexDebugEvent.EMPTY_ARRAY);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
index ddb4efc..5f94db9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
@@ -4,36 +4,18 @@
 
 package com.android.tools.r8.graph;
 
-/**
- * Wraps DexDebugInfo to make comparison and hashcode not consider
- * the SetInlineFrames
- */
-public class DexDebugInfoForWriting extends DexDebugInfo {
+import com.android.tools.r8.dex.DebugBytecodeWriter;
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
 
-  private DexDebugInfoForWriting(int startLine, DexString[] parameters, DexDebugEvent[] events) {
-    super(startLine, parameters, events);
-  }
+/** Interface to guarantee that the info only contains writable events. */
+public interface DexDebugInfoForWriting {
 
-  public static DexDebugInfoForWriting create(DexDebugInfo debugInfo) {
-    assert debugInfo != null;
-    int nonWritableEvents = 0;
-    for (DexDebugEvent event : debugInfo.events) {
-      if (!event.isWritableEvent()) {
-        nonWritableEvents++;
-      }
-    }
-    DexDebugEvent[] writableEvents;
-    if (nonWritableEvents == 0) {
-      writableEvents = debugInfo.events;
-    } else {
-      writableEvents = new DexDebugEvent[debugInfo.events.length - nonWritableEvents];
-      int i = 0;
-      for (DexDebugEvent event : debugInfo.events) {
-        if (event.isWritableEvent()) {
-          writableEvents[i++] = event;
-        }
-      }
-    }
-    return new DexDebugInfoForWriting(debugInfo.startLine, debugInfo.parameters, writableEvents);
-  }
+  void collectMixedSectionItems(MixedSectionCollection collection);
+
+  void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens);
+
+  int estimatedWriteSize();
+
+  void write(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens);
 }
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 289f255..865a536 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -992,9 +992,9 @@
   public static void setDebugInfoWithFakeThisParameter(Code code, int arity, AppView<?> appView) {
     if (code.isDexCode()) {
       DexCode dexCode = code.asDexCode();
-      dexCode.setDebugInfo(dexCode.debugInfoWithFakeThisParameter(appView.dexItemFactory()));
-      assert (dexCode.getDebugInfo() == null)
-          || (arity == dexCode.getDebugInfo().parameters.length);
+      DexDebugInfo newDebugInfo = dexCode.debugInfoWithFakeThisParameter(appView.dexItemFactory());
+      assert (newDebugInfo == null) || (arity == newDebugInfo.getParameterCount());
+      dexCode.setDebugInfo(newDebugInfo);
     } else {
       assert code.isCfCode();
       CfCode cfCode = code.asCfCode();
@@ -1222,7 +1222,7 @@
     Builder builder =
         builder(this)
             .promoteToStatic()
-            .withoutThisParameter()
+            .withoutThisParameter(appView.dexItemFactory())
             .fixupOptimizationInfo(appView, prototypeChanges.createMethodOptimizationInfoFixer())
             .setGenericSignature(MethodTypeSignature.noSignature());
     DexEncodedMethod method = builder.build();
@@ -1524,10 +1524,10 @@
       return this;
     }
 
-    public Builder withoutThisParameter() {
+    public Builder withoutThisParameter(DexItemFactory factory) {
       assert code != null;
       if (code.isDexCode()) {
-        code = code.asDexCode().withoutThisParameter();
+        code = code.asDexCode().withoutThisParameter(factory);
       } else {
         throw new Unreachable("Code " + code.getClass().getSimpleName() + " is not supported.");
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index c064992..5421ba6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -99,6 +99,8 @@
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
   private final SetOutlineFrame setOutlineFrame = new SetOutlineFrame();
   private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
+  public final DexDebugEvent.Default zeroChangeDefaultEvent = createDefault(0, 0);
+  public final DexDebugEvent.Default oneChangeDefaultEvent = createDefault(1, 1);
 
   // ReferenceTypeElement canonicalization.
   private final ConcurrentHashMap<DexType, ReferenceTypeElement> referenceTypes =
@@ -2744,6 +2746,10 @@
     }
   }
 
+  public Default createDefault(int lineDelta, int pcDelta) {
+    return createDefault(Default.create(lineDelta, pcDelta).value);
+  }
+
   public EndLocal createEndLocal(int registerNum) {
     synchronized (endLocals) {
       return endLocals.computeIfAbsent(registerNum, EndLocal::new);
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index ae16c5a..457a6f7 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -170,8 +170,8 @@
   }
 
   @Override
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
-    return asCfCode().getCodeAsInlining(caller, callee);
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
+    return asCfCode().getCodeAsInlining(caller, callee, factory);
   }
 
   public static class DebugParsingOptions {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index f2c1d8d..6dec27c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEntry;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -77,11 +78,15 @@
   private final DexMethod originalMethod;
 
   public DexSourceCode(
-      DexCode code, ProgramMethod method, DexMethod originalMethod, Position callerPosition) {
+      DexCode code,
+      ProgramMethod method,
+      DexMethod originalMethod,
+      Position callerPosition,
+      DexItemFactory factory) {
     this.code = code;
     this.method = method;
     this.originalMethod = originalMethod;
-    DexDebugInfo info = code.getDebugInfo();
+    EventBasedDebugInfo info = DexDebugInfo.convertToEventBased(code, factory);
     if (info != null) {
       debugEntries = info.computeEntries(originalMethod);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index de940f8..71db541 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -245,6 +245,7 @@
 
     private void finalizeConstantDynamicDesugaring(Consumer<ProgramMethod> needsProcessing) {
       for (ConstantDynamicClass constantDynamicClass : synthesizedConstantDynamicClasses) {
+        constantDynamicClass.rewriteBootstrapMethodSignatureIfNeeded();
         constantDynamicClass.getConstantDynamicProgramClass().forEachProgramMethod(needsProcessing);
       }
       synthesizedConstantDynamicClasses.clear();
@@ -408,6 +409,7 @@
     public void finalizeDesugaring() {
       finalizeInvokeSpecialDesugaring();
       finalizeLambdaDesugaring();
+      // TODO(b/210485236): Finalize constant dynamic desugaring by rewriting signature if needed.
     }
 
     private void finalizeInvokeSpecialDesugaring() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index fb848df..a332828 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -41,6 +42,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
@@ -51,10 +53,13 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import com.google.common.collect.ImmutableList;
@@ -80,6 +85,9 @@
   public final DexField constantValueField;
   private final DexMethod getConstMethod;
   private final Behaviour behaviour;
+  private DexMethod bootstrapMethodReference;
+  private DexMethod finalBootstrapMethodReference;
+  private boolean isFinalBootstrapMethodReferenceOnInterface;
 
   // Considered final but is set after due to circularity in allocation.
   private DexProgramClass clazz = null;
@@ -108,19 +116,49 @@
             factory.createString("get"));
 
     DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
-    DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
+    bootstrapMethodReference = bootstrapMethodHandle.asMethod();
     MethodResolutionResult resolution =
         appView
             .appInfoForDesugaring()
             .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
     if (resolution.isSingleResolution()
         && resolution.asSingleResolution().getResolvedMethod().isStatic()) {
-      // Ensure that the bootstrap method is accessible from the generated class.
       SingleResolutionResult result = resolution.asSingleResolution();
-      MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
-      flags.unsetPrivate();
-      flags.setPublic();
+      if (bootstrapMethodHandle.isInterface
+          && appView.options().isInterfaceMethodDesugaringEnabled()) {
+        bootstrapMethodReference =
+            bootstrapMethodReference.withHolder(
+                InterfaceDesugaringSyntheticHelper.getCompanionClassType(
+                    bootstrapMethodReference.getHolderType(), factory),
+                factory);
+        isFinalBootstrapMethodReferenceOnInterface = false;
+      } else {
+        assert bootstrapMethodReference.getHolderType() == resolution.getResolvedHolder().getType();
+        isFinalBootstrapMethodReferenceOnInterface = bootstrapMethodHandle.isInterface;
+      }
+      if (shouldRewriteBootstrapMethodSignature()) {
+        // The bootstrap method will have its signature modified to have type Object as its first
+        // argument.
+        this.finalBootstrapMethodReference =
+            factory.createMethod(
+                bootstrapMethodReference.getHolderType(),
+                factory.createProto(
+                    bootstrapMethodReference.getReturnType(),
+                    factory.objectType,
+                    factory.stringType,
+                    factory.classType),
+                bootstrapMethodReference.getName());
+      } else {
+        this.finalBootstrapMethodReference = bootstrapMethodReference;
+        // Ensure that the bootstrap method is accessible from the generated class.
+        DexEncodedMethod bootstrapMethodImpl = result.getResolvedMethod();
+        MethodAccessFlags flags = bootstrapMethodImpl.getAccessFlags();
+        flags.unsetPrivate();
+        flags.setPublic();
+      }
+
       behaviour = CACHE_CONSTANT;
+
       synthesizeConstantDynamicClass(builder);
     } else {
       // Unconditionally throw as the RI.
@@ -128,6 +166,12 @@
     }
   }
 
+  private boolean shouldRewriteBootstrapMethodSignature() {
+    // TODO(b/210485236): Check for references to the bootstrap method outside of dynamic constant.
+    return !appView.enableWholeProgramOptimizations()
+        && appView.options().getMinApiLevel().isLessThan(AndroidApiLevel.O);
+  }
+
   public Collection<CfInstruction> desugarConstDynamicInstruction(
       CfConstDynamic invoke,
       FreshLocalProvider freshLocalProvider,
@@ -214,13 +258,15 @@
 
   private void invokeBootstrapMethod(ImmutableList.Builder<CfInstruction> instructions) {
     assert reference.getBootstrapMethod().type.isInvokeStatic();
-    DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
-    DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
     // TODO(b/178172809): Use MethodHandle.invokeWithArguments if supported.
     instructions.add(new CfConstNull());
     instructions.add(new CfConstString(reference.getName()));
     instructions.add(new CfConstClass(reference.getType()));
-    instructions.add(new CfInvoke(INVOKESTATIC, bootstrapMethodReference, false));
+    instructions.add(
+        new CfInvoke(
+            INVOKESTATIC,
+            finalBootstrapMethodReference,
+            isFinalBootstrapMethodReferenceOnInterface));
     instructions.add(new CfCheckCast(reference.getType()));
   }
 
@@ -317,4 +363,76 @@
     assert clazz != null;
     this.clazz = clazz;
   }
+
+  public void rewriteBootstrapMethodSignatureIfNeeded() {
+    if (!shouldRewriteBootstrapMethodSignature() || behaviour != CACHE_CONSTANT) {
+      return;
+    }
+    DexProgramClass bootstrapMethodHolder =
+        appView.definitionFor(bootstrapMethodReference.getHolderType()).asProgramClass();
+    DexEncodedMethod replacement =
+        bootstrapMethodHolder
+            .getMethodCollection()
+            .replaceDirectMethod(
+                bootstrapMethodReference,
+                encodedMethod -> {
+                  MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
+                  // Ensure that the bootstrap method is accessible from the generated class.
+                  newAccessFlags.unsetPrivate();
+                  newAccessFlags.setPublic();
+                  DexEncodedMethod newMethod =
+                      DexEncodedMethod.syntheticBuilder()
+                          .setMethod(finalBootstrapMethodReference)
+                          .setAccessFlags(newAccessFlags)
+                          .setGenericSignature(encodedMethod.getGenericSignature())
+                          .setAnnotations(encodedMethod.annotations())
+                          .setParameterAnnotations(encodedMethod.parameterAnnotationsList)
+                          .setCode(adaptCode(encodedMethod))
+                          .setApiLevelForDefinition(encodedMethod.getApiLevelForDefinition())
+                          .setApiLevelForCode(encodedMethod.getApiLevelForCode())
+                          .build();
+                  newMethod.copyMetadata(appView, encodedMethod);
+                  return newMethod;
+                });
+    if (replacement != null) {
+      // Since we've copied the code object from an existing method, the code should already be
+      // processed, and thus we don't need to schedule it for processing in D8.
+      assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
+      assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
+    }
+    // The method might already have been moved by another dynamic constant targeting it.
+    // If so, it must be defined on the holder.
+    ProgramMethod modified =
+        bootstrapMethodHolder.lookupProgramMethod(finalBootstrapMethodReference);
+    assert modified != null;
+    assert modified.getDefinition().isPublicMethod();
+  }
+
+  private DexType mapLookupTypeToObject(DexType type) {
+    return type == appView.dexItemFactory().lookupType ? appView.dexItemFactory().objectType : type;
+  }
+
+  private Code adaptCode(DexEncodedMethod method) {
+    assert behaviour == CACHE_CONSTANT;
+    if (method.getCode().isDexCode()) {
+      return method.getCode();
+    }
+    CfCode code = method.getCode().asCfCode();
+    List<CfInstruction> newInstructions =
+        ListUtils.mapOrElse(
+            code.getInstructions(),
+            instruction ->
+                instruction.isFrame()
+                    ? instruction.asFrame().map(this::mapLookupTypeToObject)
+                    : instruction);
+    return code.getInstructions() != newInstructions
+        ? new CfCode(
+            method.getHolderType(),
+            code.getMaxStack(),
+            code.getMaxLocals(),
+            newInstructions,
+            code.getTryCatchRanges(),
+            code.getLocalVariables())
+        : code;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 616cc07..77d99e5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -155,7 +155,7 @@
   }
 
   // Gets the companion class for the interface `type`.
-  static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
+  public static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
     assert type.isClassType();
     String descriptor = type.descriptor.toString();
     String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index c89f90c..82f80e8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -168,7 +168,10 @@
       companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion());
     }
     Code code =
-        definition.getCode().getCodeAsInlining(companion.getReference(), method.getReference());
+        definition
+            .getCode()
+            .getCodeAsInlining(
+                companion.getReference(), method.getReference(), appView.dexItemFactory());
     if (!definition.isStatic()) {
       DexEncodedMethod.setDebugInfoWithFakeThisParameter(
           code, companion.getReference().getArity(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 3875ddf..d35c7b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -247,7 +247,7 @@
             }
           }
           if (outValue.getType().isNullType()) {
-            addNullDependencies(code, outValue.uniqueUsers(), eligibleEnums);
+            addNullDependencies(code, outValue, eligibleEnums);
           }
         } else {
           if (instruction.isInvokeMethod()) {
@@ -290,7 +290,7 @@
           }
         }
         if (phi.getType().isNullType()) {
-          addNullDependencies(code, phi.uniqueUsers(), eligibleEnums);
+          addNullDependencies(code, phi, eligibleEnums);
         }
       }
     }
@@ -520,8 +520,8 @@
         || method == factory.classMethods.getSimpleName;
   }
 
-  private void addNullDependencies(IRCode code, Set<Instruction> uses, Set<DexType> eligibleEnums) {
-    for (Instruction use : uses) {
+  private void addNullDependencies(IRCode code, Value nullValue, Set<DexType> eligibleEnums) {
+    for (Instruction use : nullValue.uniqueUsers()) {
       if (use.isInvokeMethod()) {
         InvokeMethod invokeMethod = use.asInvokeMethod();
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
@@ -530,7 +530,8 @@
             eligibleEnums.add(paramType);
           }
         }
-        if (invokeMethod.isInvokeMethodWithReceiver()) {
+        if (invokeMethod.isInvokeMethodWithReceiver()
+            && invokeMethod.asInvokeMethodWithReceiver().getReceiver() == nullValue) {
           DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
           if (enumClass != null) {
             markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 33f4ba5..dca8e31 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -356,6 +356,10 @@
             if (!candidateBuilders.contains(builder)) {
               continue;
             }
+            if (builder.hasPhiUsers()) {
+              candidateBuilders.remove(builder);
+              continue;
+            }
             Map<Instruction, BuilderState> perInstrState = createBuilderState(builder);
             perInstrState.put(instr, BuilderState.createRoot());
             continue;
@@ -396,6 +400,10 @@
             if (!candidateBuilders.contains(builder)) {
               continue;
             }
+            if (invoke.hasUsedOutValue()) {
+              candidateBuilders.remove(builder);
+              continue;
+            }
             Value arg = invoke.inValues().get(1).getAliasedValue();
             DexType argType = invoke.getInvokedMethod().proto.parameters.values[0];
             String addition = extractConstantArgument(arg, argType);
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index bec8073..f948457 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -180,6 +180,7 @@
 
               ReservedFieldNamingState reservedNames =
                   getOrCreateReservedFieldNamingState(clazz.type);
+              // TODO(b/213041051): This could avoid duplication of strings downwards.
               FieldNamingState state = parentState.createChildState(reservedNames);
               if (clazz.isProgramClass()) {
                 clazz.asProgramClass().forEachProgramField(field -> renameField(field, state));
@@ -191,8 +192,8 @@
   }
 
   private void renameFieldsInInterfaces(Collection<DexClass> interfaces) {
-    InterfacePartitioning partioning = new InterfacePartitioning(this);
-    for (Set<DexClass> partition : partioning.sortedPartitions(interfaces)) {
+    InterfacePartitioning partitioning = new InterfacePartitioning(this);
+    for (Set<DexClass> partition : partitioning.sortedPartitions(interfaces)) {
       renameFieldsInInterfacePartition(partition);
     }
   }
@@ -231,11 +232,10 @@
         if (!visited.add(implementationType)) {
           continue;
         }
-
         DexClass implementation = appView.definitionFor(implementationType);
         if (implementation != null) {
           getOrCreateReservedFieldNamingState(implementationType)
-              .includeReservations(namesToBeReservedInImplementsSubclasses);
+              .setInterfaceMinificationState(namesToBeReservedInImplementsSubclasses);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 3ba2467..8067817 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -654,9 +654,8 @@
                             DexType frontierType = minifierState.getFrontier(clazz.type);
                             iState.addReservationType(frontierType);
                             // The reservation state should already be added, but if a class is
-                            // extending
-                            // an interface, we will not visit the class during the sub-type
-                            // traversel
+                            // extending an interface, we will not visit the class during the
+                            // sub-type traversel.
                             if (minifierState.getReservationState(clazz.type) == null) {
                               minifierState.allocateReservationStateAndReserve(
                                   clazz.type, frontierType);
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
index 4e3e98c..258646e 100644
--- a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -14,16 +14,32 @@
 
 class ReservedFieldNamingState extends FieldNamingStateBase<InternalState> {
 
+  private ReservedFieldNamingState interfaceMinificationState = null;
+
   ReservedFieldNamingState(AppView<? extends AppInfoWithClassHierarchy> appView) {
     super(appView, new IdentityHashMap<>());
   }
 
   boolean isReserved(DexString name, DexType type) {
-    return getReservedByName(name, type) != null;
+    return getReservedByName(name, type) != null
+        || getReservedByNameInInterfaces(name, type) != null;
   }
 
-  DexString getReservedByName(DexString name, DexType type) {
-    InternalState internalState = getInternalState(type);
+  private DexString getReservedByName(DexString name, DexType type) {
+    DexString reservedByNameInState = getReservedByNameInState(getInternalState(type), name);
+    if (reservedByNameInState != null) {
+      return reservedByNameInState;
+    }
+    return getReservedByNameInInterfaces(name, type);
+  }
+
+  private DexString getReservedByNameInInterfaces(DexString name, DexType type) {
+    return interfaceMinificationState == null
+        ? null
+        : getReservedByNameInState(interfaceMinificationState.getInternalState(type), name);
+  }
+
+  private static DexString getReservedByNameInState(InternalState internalState, DexString name) {
     return internalState == null ? null : internalState.getReservedByName(name);
   }
 
@@ -35,12 +51,28 @@
     for (Map.Entry<DexType, InternalState> entry : reservedNames.internalStates.entrySet()) {
       getOrCreateInternalState(entry.getKey()).includeReservations(entry.getValue());
     }
+    includeInterfaceReservationState(reservedNames);
   }
 
   void includeReservationsFromBelow(ReservedFieldNamingState reservedNames) {
     for (Map.Entry<DexType, InternalState> entry : reservedNames.internalStates.entrySet()) {
       getOrCreateInternalState(entry.getKey()).includeReservationsFromBelow(entry.getValue());
     }
+    includeInterfaceReservationState(reservedNames);
+  }
+
+  private void includeInterfaceReservationState(ReservedFieldNamingState reservedNames) {
+    if (reservedNames.interfaceMinificationState != null) {
+      assert interfaceMinificationState == null
+          || interfaceMinificationState == reservedNames.interfaceMinificationState;
+      interfaceMinificationState = reservedNames.interfaceMinificationState;
+    }
+  }
+
+  void setInterfaceMinificationState(ReservedFieldNamingState namingState) {
+    assert namingState != null;
+    assert interfaceMinificationState == null;
+    this.interfaceMinificationState = namingState;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 24c6a60..06fdc4c 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -131,6 +131,10 @@
     return results != null ? results.toArray(emptyArray) : original;
   }
 
+  public static <T> T[] filter(T[] original, Predicate<T> test, T[] emptyArray) {
+    return map(original, e -> test.test(e) ? e : null, emptyArray);
+  }
+
   public static int[] createIdentityArray(int size) {
     int[] array = new int[size];
     for (int i = 0; i < size; i++) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 0bfc079..1000a75 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1869,8 +1869,12 @@
     return sourceFileProvider != null && sourceFileProvider.allowDiscardingSourceFile();
   }
 
-  public boolean canUseDexPcAsDebugInformation() {
-    return lineNumberOptimization == LineNumberOptimization.ON
+  public boolean canUseDexPc2PcAsDebugInformation() {
+    return lineNumberOptimization == LineNumberOptimization.ON;
+  }
+
+  public boolean canUseNativeDexPcInsteadOfDebugInfo() {
+    return canUseDexPc2PcAsDebugInformation()
         && hasMinApi(AndroidApiLevel.O)
         && allowDiscardingResidualDebugInfo();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 71da5cc..50eeb68 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexDebugEventBuilder;
 import com.android.tools.r8.graph.DexDebugEventVisitor;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
 import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -73,7 +74,10 @@
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -345,6 +349,117 @@
     return mapId;
   }
 
+  private interface PcBasedDebugInfoRecorder {
+    /** Callback to record a code object with a given max instruction PC and parameter count. */
+    void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount);
+
+    /** Callback to record a code object with only a single "line". */
+    void recordSingleLineFor(DexCode code, int parameterCount);
+
+    void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc);
+
+    /**
+     * Install the correct debug info objects.
+     *
+     * <p>Must be called after all recordings have been given to allow computing the debug info
+     * items to be installed.
+     */
+    void updateDebugInfoInCodeObjects();
+  }
+
+  private static class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder {
+
+    // Some DEX VMs require matching parameter count in methods and debug info.
+    // Record the max pc for each parameter count so we can share the param count objects.
+    private Int2IntMap paramToMaxPc = new Int2IntOpenHashMap();
+
+    private final List<Pair<Integer, DexCode>> codesToUpdate = new ArrayList<>();
+
+    // We can only drop single-line debug info if it is OK to lose the source-file info.
+    // This list is null if we must retain single-line entries.
+    private final List<DexCode> singleLineCodesToClear;
+
+    public Pc2PcMappingSupport(boolean allowDiscardingSourceFile) {
+      singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
+    }
+
+    @Override
+    public void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount) {
+      codesToUpdate.add(new Pair<>(parameterCount, code));
+      int existing = paramToMaxPc.getOrDefault(parameterCount, -1);
+      if (existing < lastInstructionPc) {
+        paramToMaxPc.put(parameterCount, lastInstructionPc);
+      }
+    }
+
+    @Override
+    public void recordSingleLineFor(DexCode code, int parameterCount) {
+      if (singleLineCodesToClear != null) {
+        singleLineCodesToClear.add(code);
+        return;
+      }
+      int lastInstructionPc = ArrayUtils.last(code.instructions).getOffset();
+      recordPcMappingFor(code, lastInstructionPc, parameterCount);
+    }
+
+    @Override
+    public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
+      if (singleLineCodesToClear != null) {
+        singleLineCodesToClear.add(code);
+        return;
+      }
+      recordPcMappingFor(code, lastInstructionPc, parameterCount);
+    }
+
+    @Override
+    public void updateDebugInfoInCodeObjects() {
+      Int2ReferenceMap<DexDebugInfo> debugInfos =
+          new Int2ReferenceOpenHashMap<>(paramToMaxPc.size());
+      codesToUpdate.forEach(
+          entry -> {
+            int parameterCount = entry.getFirst();
+            DexCode code = entry.getSecond();
+            DexDebugInfo debugInfo =
+                debugInfos.computeIfAbsent(
+                    parameterCount,
+                    key -> buildPc2PcDebugInfo(paramToMaxPc.get(key), parameterCount));
+            code.setDebugInfo(debugInfo);
+          });
+      if (singleLineCodesToClear != null) {
+        singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
+      }
+    }
+
+    private static DexDebugInfo buildPc2PcDebugInfo(int lastInstructionPc, int parameterCount) {
+      return new DexDebugInfo.PcBasedDebugInfo(parameterCount, lastInstructionPc);
+    }
+  }
+
+  private static class NativePcSupport implements PcBasedDebugInfoRecorder {
+
+    @Override
+    public void recordPcMappingFor(DexCode code, int lastInstructionPc, int length) {
+      // Strip the info in full as the runtime will emit the PC directly.
+      code.setDebugInfo(null);
+    }
+
+    @Override
+    public void recordSingleLineFor(DexCode code, int parameterCount) {
+      // Strip the info at once as it does not conflict with any PC mapping update.
+      code.setDebugInfo(null);
+    }
+
+    @Override
+    public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
+      recordSingleLineFor(code, parameterCount);
+    }
+
+    @Override
+    public void updateDebugInfoInCodeObjects() {
+      // Already null out the info so nothing to do.
+    }
+  }
+
   public static ClassNameMapper run(
       AppView<?> appView,
       DexApplication application,
@@ -358,6 +473,11 @@
 
     Map<DexMethod, OutlineFixupBuilder> outlinesToFix = new IdentityHashMap<>();
 
+    PcBasedDebugInfoRecorder pcBasedDebugInfo =
+        appView.options().canUseNativeDexPcInsteadOfDebugInfo()
+            ? new NativePcSupport()
+            : new Pc2PcMappingSupport(appView.options().allowDiscardingResidualDebugInfo());
+
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : application.classes()) {
       boolean isSyntheticClass = appView.getSyntheticItems().isSyntheticClass(clazz);
@@ -440,11 +560,13 @@
           List<MappedPosition> mappedPositions;
           Code code = method.getCode();
           boolean canUseDexPc =
-              appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1;
+              appView.options().canUseDexPc2PcAsDebugInformation() && methods.size() == 1;
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
               if (canUseDexPc) {
-                mappedPositions = optimizeDexCodePositionsForPc(method, appView, kotlinRemapper);
+                mappedPositions =
+                    optimizeDexCodePositionsForPc(
+                        method, appView, kotlinRemapper, pcBasedDebugInfo);
               } else {
                 mappedPositions =
                     optimizeDexCodePositions(
@@ -615,8 +737,8 @@
           if (method.getCode().isDexCode()
               && method.getCode().asDexCode().getDebugInfo()
                   == DexDebugInfoForSingleLineMethod.getInstance()) {
-            assert appView.options().allowDiscardingResidualDebugInfo();
-            method.getCode().asDexCode().setDebugInfo(null);
+            pcBasedDebugInfo.recordSingleLineFor(
+                method.getCode().asDexCode(), method.getParameters().size());
           }
         } // for each method of the group
       } // for each method group, grouped by name
@@ -625,6 +747,9 @@
     // Fixup all outline positions
     outlinesToFix.values().forEach(OutlineFixupBuilder::fixup);
 
+    // Update all the debug-info objects.
+    pcBasedDebugInfo.updateDebugInfoInCodeObjects();
+
     return classNameMapperBuilder.build();
   }
 
@@ -727,7 +852,7 @@
     }
     if (code.isDexCode()) {
       DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
-      return dexDebugInfo == null ? 0 : dexDebugInfo.startLine;
+      return dexDebugInfo == null ? 0 : dexDebugInfo.getStartLine();
     } else if (code.isCfCode()) {
       List<CfInstruction> instructions = code.asCfCode().getInstructions();
       for (CfInstruction instruction : instructions) {
@@ -823,7 +948,10 @@
     if (debugInfo == null) {
       return false;
     }
-    for (DexDebugEvent event : debugInfo.events) {
+    if (debugInfo.isPcBasedInfo()) {
+      return true;
+    }
+    for (DexDebugEvent event : debugInfo.asEventBasedInfo().events) {
       if (event instanceof DexDebugEvent.Default) {
         return true;
       }
@@ -851,7 +979,10 @@
     // Do the actual processing for each method.
     DexApplication application = appView.appInfo().app();
     DexCode dexCode = method.getCode().asDexCode();
-    DexDebugInfo debugInfo = dexCode.getDebugInfo();
+    // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
+    EventBasedDebugInfo debugInfo =
+        DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
+    assert debugInfo != null;
     List<DexDebugEvent> processedEvents = new ArrayList<>();
 
     PositionEventEmitter positionEventEmitter =
@@ -949,8 +1080,8 @@
       return mappedPositions;
     }
 
-    DexDebugInfo optimizedDebugInfo =
-        new DexDebugInfo(
+    EventBasedDebugInfo optimizedDebugInfo =
+        new EventBasedDebugInfo(
             positionEventEmitter.getStartLine(),
             debugInfo.parameters,
             processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
@@ -985,14 +1116,19 @@
   }
 
   private static List<MappedPosition> optimizeDexCodePositionsForPc(
-      DexEncodedMethod method, AppView<?> appView, PositionRemapper positionRemapper) {
+      DexEncodedMethod method,
+      AppView<?> appView,
+      PositionRemapper positionRemapper,
+      PcBasedDebugInfoRecorder debugInfoProvider) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexCode dexCode = method.getCode().asDexCode();
-    DexDebugInfo debugInfo = dexCode.getDebugInfo();
-
+    // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
+    EventBasedDebugInfo debugInfo =
+        DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
+    assert debugInfo != null;
+    BooleanBox singleOriginalLine = new BooleanBox(true);
     Pair<Integer, Position> lastPosition = new Pair<>();
-
     DexDebugEventVisitor visitor =
         new DexDebugPositionState(
             debugInfo.startLine,
@@ -1001,7 +1137,12 @@
           public void visit(Default defaultEvent) {
             super.visit(defaultEvent);
             assert getCurrentLine() >= 0;
+            Position currentPosition = getPositionFromPositionState(this);
             if (lastPosition.getSecond() != null) {
+              if (singleOriginalLine.isTrue()
+                  && !currentPosition.equals(lastPosition.getSecond())) {
+                singleOriginalLine.set(false);
+              }
               remapAndAddForPc(
                   lastPosition.getFirst(),
                   getCurrentPc(),
@@ -1010,7 +1151,7 @@
                   mappedPositions);
             }
             lastPosition.setFirst(getCurrentPc());
-            lastPosition.setSecond(getPositionFromPositionState(this));
+            lastPosition.setSecond(currentPosition);
             resetOutlineInformation();
           }
         };
@@ -1019,22 +1160,32 @@
       event.accept(visitor);
     }
 
+    int lastInstructionPc = ArrayUtils.last(dexCode.instructions).getOffset();
     if (lastPosition.getSecond() != null) {
-      int lastPc = dexCode.instructions[dexCode.instructions.length - 1].getOffset();
       remapAndAddForPc(
           lastPosition.getFirst(),
-          lastPc + 1,
+          lastInstructionPc + 1,
           lastPosition.getSecond(),
           positionRemapper,
           mappedPositions);
     }
 
-    dexCode.setDebugInfo(null);
+    // If we only have one original line we can always retrace back uniquely.
+    assert !mappedPositions.isEmpty();
+    if (singleOriginalLine.isTrue()
+        && lastPosition.getSecond() != null
+        && !mappedPositions.get(0).isOutlineCaller()) {
+      dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
+      debugInfoProvider.recordSingleLineFor(
+          dexCode, method.getParameters().size(), lastInstructionPc);
+    } else {
+      debugInfoProvider.recordPcMappingFor(dexCode, lastInstructionPc, debugInfo.parameters.length);
+    }
     return mappedPositions;
   }
 
   private static boolean verifyIdentityMapping(
-      DexDebugInfo originalDebugInfo, DexDebugInfo optimizedDebugInfo) {
+      EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) {
     assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
     assert optimizedDebugInfo.events.length == originalDebugInfo.events.length;
     for (int i = 0; i < originalDebugInfo.events.length; ++i) {
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index 04d801c..be77df56 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -59,8 +59,13 @@
         }
 
         @Override
-        public void scope(String title, TimingScope fn) {
-          // Ignore.
+        public <T> T scope(String title, TimingScope<T> fn) {
+          return fn.apply();
+        }
+
+        @Override
+        public void vscope(String title, VoidTimingScope fn) {
+          fn.apply();
         }
       };
 
@@ -402,7 +407,7 @@
     top.report(0, top);
   }
 
-  public void scope(String title, TimingScope fn) {
+  public void vscope(String title, VoidTimingScope fn) {
     begin(title);
     try {
       fn.apply();
@@ -411,7 +416,20 @@
     }
   }
 
-  public interface TimingScope {
+  public <T> T scope(String title, TimingScope<T> fn) {
+    begin(title);
+    try {
+      return fn.apply();
+    } finally {
+      end();
+    }
+  }
+
+  public interface TimingScope<T> {
+    T apply();
+  }
+
+  public interface VoidTimingScope {
     void apply();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index aec53db..ae8a263 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -61,6 +61,23 @@
     return self;
   }
 
+  public T applyIf(
+      boolean value,
+      ThrowableConsumer<T> trueConsumer,
+      boolean value2,
+      ThrowableConsumer<T> trueConsumer2,
+      ThrowableConsumer<T> falseConsumer) {
+    T self = self();
+    if (value) {
+      trueConsumer.acceptWithRuntimeException(self);
+    } else if (value2) {
+      trueConsumer2.acceptWithRuntimeException(self);
+    } else {
+      falseConsumer.acceptWithRuntimeException(self);
+    }
+    return self;
+  }
+
   @Deprecated
   public RR run(String mainClass)
       throws CompilationFailedException, ExecutionException, IOException {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
index c41c5e2..4b94e1c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -48,6 +48,7 @@
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::disableCheckAllApiReferencesAreNotUnknown)
         .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .compile()
         .addRunClasspathClasses(Api.class)
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
index 302cca5..f946a84 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
@@ -47,6 +47,7 @@
         .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java
index 911731b..fc29914 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java
@@ -45,6 +45,7 @@
         .addKeepMainRule(Main.class)
         .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .enableInliningAnnotations()
         .compile()
         .inspect(inspector -> verifyThat(inspector, parameters, callApi).inlinedInto(callCallApi))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
index 9d667c1..73132ae 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -48,6 +48,7 @@
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForClass(ApiType.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .compile()
         .addRunClasspathClasses(ApiType.class)
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
index c4308ed..53b4942 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
@@ -47,6 +47,7 @@
         .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
index 4979620..9a239f8 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
@@ -55,6 +55,7 @@
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
index db7dc06..71a742e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
@@ -52,6 +52,7 @@
         .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (methodReference, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index 3ccf46d..605865f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -11,11 +11,10 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexDebugEntry;
 import com.android.tools.r8.graph.DexDebugEntryBuilder;
-import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 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.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -36,15 +35,18 @@
   private final DexEncodedMethod method;
   private final List<DexDebugEntry> entries;
 
-  final DexDebugInfo info;
+  final EventBasedDebugInfo info;
 
   public DebugInfoInspector(DexEncodedMethod method, DexItemFactory factory) {
     this.method = method;
-    info = method.getCode().asDexCode().getDebugInfo();
-    if (info != null) {
+    EventBasedDebugInfo debugInfo =
+        DexDebugInfo.convertToEventBased(method.getCode().asDexCode(), factory);
+    if (debugInfo != null) {
+      info = debugInfo;
       entries = new DexDebugEntryBuilder(method, factory).build();
       checkConsistentEntries();
     } else {
+      info = null;
       entries = Collections.emptyList();
     }
   }
@@ -58,24 +60,6 @@
     this(new CodeInspector(app), clazz, method);
   }
 
-  public boolean hasLocalsInfo() {
-    DexDebugInfo dexInfo = method.getCode().asDexCode().getDebugInfo();
-    if (info == null || dexInfo == null) {
-      return false;
-    }
-    for (DexString parameter : dexInfo.parameters) {
-      if (parameter != null) {
-        return true;
-      }
-    }
-    for (DexDebugEvent event : dexInfo.events) {
-      if (event instanceof DexDebugEvent.StartLocal) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   public void checkStartLine(int i) {
     assertEquals(i, info.startLine);
   }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
index e64b7d7..008d3cf 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
@@ -71,6 +71,7 @@
                               .getCode()
                               .asDexCode()
                               .getDebugInfo()
+                              .asEventBasedInfo()
                               .events)
                       .anyMatch(e -> e instanceof SetFile));
             })
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
index a2f71b4..c31af39 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceFrameResult;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -68,7 +67,6 @@
         .addKeepMainRule(MAIN)
         .addKeepMethodRules(MAIN, "void overloaded(...)")
         .addKeepAttributeLineNumberTable()
-        .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .enableAlwaysInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java
index de9685e..4810b1d 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java
@@ -34,7 +34,7 @@
         getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
-  private static final String EXPECTED_OUTPUT =
+  private static final String EXPECTED_OUTPUT_WITH_METHOD_HANDLES =
       StringUtils.lines(
           "Hello, world!",
           "myConstant",
@@ -42,6 +42,14 @@
           "java.lang.invoke.MethodHandles$Lookup",
           "java.lang.String",
           "java.lang.Class");
+  private static final String EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES =
+      StringUtils.lines(
+          "Hello, world!",
+          "myConstant",
+          "3",
+          "java.lang.Object",
+          "java.lang.String",
+          "java.lang.Class");
   private static final String EXPECTED_OUTPUT_R8 =
       StringUtils.lines("Hello, world!", "No myConstant method");
 
@@ -56,7 +64,7 @@
     testForJvm()
         .addProgramClassFileData(getTransformedClasses())
         .run(parameters.getRuntime(), MAIN_CLASS)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+        .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES);
   }
 
   @Test
@@ -64,24 +72,22 @@
     testForDesugaring(parameters)
         .addProgramClassFileData(getTransformedClasses())
         .run(parameters.getRuntime(), MAIN_CLASS)
-        // TODO(b/210485236): This should never fail.
         .applyIf(
             // When not desugaring the CF code requires JDK 11.
             DesugarTestConfiguration::isNotDesugared,
             r -> {
               if (parameters.isCfRuntime()
                   && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
-                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES);
               } else {
                 r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
               }
             },
             c ->
                 DesugarTestConfiguration.isDesugared(c)
-                    && parameters.isDexRuntime()
-                    && parameters.asDexRuntime().getVersion().isOlderThan(Version.V8_1_0),
-            r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
-            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+                    && parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES));
   }
 
   @Test
@@ -110,11 +116,10 @@
             parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
             b -> b.addDontWarn(MethodHandles.Lookup.class))
         .run(parameters.getRuntime(), MAIN_CLASS)
-        // TODO(b/210485236): This should never fail.
         .applyIf(
             parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0),
             b -> b.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
-            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT));
+            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES));
   }
 
   private byte[] getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java
new file mode 100644
index 0000000..a09ae6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2022, 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.desugar.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstantDynamicInDefaultInterfaceMethodTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+    assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+    testForJvm()
+        .addProgramClasses(MAIN_CLASS)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClasses(MAIN_CLASS)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MAIN_CLASS)
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_CLASS)
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            b -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      b.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            // TODO(b/210485236): This should not fail for R8.
+            !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
+            b ->
+                b.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b ->
+                b.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  private byte[] getTransformedClasses() throws Exception {
+    return transformer(I.class)
+        .setVersion(CfVersion.V11)
+        .transformConstStringToConstantDynamic(
+            "condy1", I.class, "myConstant", "constantName", Object.class)
+        .transformConstStringToConstantDynamic(
+            "condy2", I.class, "myConstant", "constantName", Object.class)
+        .setPrivate(
+            I.class.getDeclaredMethod(
+                "myConstant", MethodHandles.Lookup.class, String.class, Class.class))
+        .transform();
+  }
+
+  public interface I {
+
+    default Object f() {
+      return "condy1"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    default Object g() {
+      return "condy2"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    /* private */ static Object myConstant(
+        MethodHandles.Lookup lookup, String name, Class<?> type) {
+      return new Object();
+    }
+  }
+
+  public static class A implements I {
+    public static void main(String[] args) {
+      A a = new A();
+      System.out.println(a.f() != null);
+      System.out.println(a.f() == a.g());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java
index 79fc834..8524f5d 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java
@@ -49,13 +49,20 @@
   public static JacocoClasses testClasses;
 
   private static final String MAIN_CLASS = TestRunner.class.getTypeName();
-  private static final String EXPECTED_OUTPUT =
+  private static final String EXPECTED_OUTPUT_WITH_METHOD_HANDLES =
       StringUtils.lines(
           jacocoBootstrapMethodName,
           "3",
           "java.lang.invoke.MethodHandles$Lookup",
           "java.lang.String",
           "java.lang.Class");
+  private static final String EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES =
+      StringUtils.lines(
+          jacocoBootstrapMethodName,
+          "3",
+          "java.lang.Object",
+          "java.lang.String",
+          "java.lang.Class");
 
   @BeforeClass
   public static void setUpInput() throws IOException {
@@ -85,7 +92,7 @@
         .addProgramFiles(testClasses.getOriginal())
         .enableJaCoCoAgent(ToolHelper.JACOCO_AGENT, agentOutputOnTheFly)
         .run(parameters.getRuntime(), MAIN_CLASS)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+        .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES);
     checkJacocoReport(agentOutputOnTheFly);
 
     // Run the instrumented code.
@@ -94,7 +101,7 @@
         .addProgramFiles(testClasses.getInstrumented())
         .configureJaCoCoAgentForOfflineInstrumentedCode(ToolHelper.JACOCO_AGENT, agentOutputOffline)
         .run(parameters.getRuntime(), MAIN_CLASS)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+        .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES);
     checkJacocoReport(agentOutputOffline);
   }
 
@@ -108,11 +115,10 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .runWithJaCoCo(agentOutput, parameters.getRuntime(), MAIN_CLASS)
-        // TODO(b/210485236): This should never fail.
         .applyIf(
-            parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0),
-            b -> b.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
-            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT));
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES),
+            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES));
     checkJacocoReport(agentOutput);
   }
 
@@ -160,11 +166,10 @@
             "javax.management.**")
         .compile()
         .runWithJaCoCo(agentOutput, parameters.getRuntime(), MAIN_CLASS)
-        // TODO(b/210485236): This should never fail.
         .applyIf(
             parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0),
             b -> b.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
-            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT));
+            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES));
     checkJacocoReport(agentOutput);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
index 9c33f50..46261e5 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.jacoco.JacocoClasses;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -50,7 +51,7 @@
   public JacocoClasses testClasses;
 
   private static final String MAIN_CLASS = TestRunner.class.getTypeName();
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!", "Hello from I!");
 
   @BeforeClass
   public static void setUpInput() throws IOException {
@@ -85,7 +86,7 @@
         .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
     List<String> onTheFlyReport = testClasses.generateReport(agentOutputOnTheFly);
-    assertEquals(2, onTheFlyReport.size());
+    assertEquals(3, onTheFlyReport.size());
 
     // Run the instrumented code.
     Path agentOutputOffline = output.resolve("offline");
@@ -114,7 +115,7 @@
       // TODO(sgjesse): Need to figure out why there is no instrumentation output for newer VMs.
       if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
         List<String> report = testClasses.generateReport(agentOutput);
-        assertEquals(2, report.size());
+        assertEquals(3, report.size());
       } else {
         assertFalse(Files.exists(agentOutput));
       }
@@ -130,16 +131,23 @@
   private static JacocoClasses testClasses(TemporaryFolder temp, CfVersion version)
       throws IOException {
     return new JacocoClasses(
-        transformer(TestRunner.class)
-            .setVersion(version) /*.setClassDescriptor("LTestRunner;")*/
-            .transform(),
+        ImmutableList.of(
+            transformer(TestRunner.class).setVersion(version).transform(),
+            transformer(I.class).setVersion(version).transform()),
         temp);
   }
 
-  static class TestRunner {
+  interface I {
+    default void m() {
+      System.out.println("Hello from I!");
+    }
+  }
+
+  static class TestRunner implements I {
 
     public static void main(String[] args) {
       System.out.println("Hello, world!");
+      new TestRunner().m();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java
new file mode 100644
index 0000000..2d4640a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.FormatStyle;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DateTimeFormatterTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final String expectedOutputDesugaredLib =
+      StringUtils.lines("2/3/01 4:05 AM - Feb 3, 1 4:05 AM");
+  private static final String expectedOutput =
+      StringUtils.lines("2/3/01, 4:05 AM - Feb 3, 1, 4:05 AM");
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public DateTimeFormatterTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    D8TestRunResult run =
+        testForD8()
+            .addLibraryFiles(getLibraryFile())
+            .addInnerClasses(DateTimeFormatterTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .setIncludeClassesChecksum(true)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), TestClass.class);
+    if (requiresTimeDesugaring(parameters)) {
+      run.assertSuccessWithOutput(expectedOutputDesugaredLib);
+    } else {
+      run.assertSuccessWithOutput(expectedOutput);
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addLibraryFiles(getLibraryFile())
+            .noMinification()
+            .addKeepMainRule(TestClass.class)
+            .addInnerClasses(DateTimeFormatterTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), TestClass.class);
+    if (requiresTimeDesugaring(parameters)) {
+      run.assertSuccessWithOutput(expectedOutputDesugaredLib);
+    } else {
+      run.assertSuccessWithOutput(expectedOutput);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // See b/210885987: ANRS reported on Android 12 on the methods:
+      // - j$.time.format.DateTimeFormatterBuilder.append (DateTimeFormatterBuilder.java)
+      // - j$.time.format.DateTimeFormatter.<clinit> (DateTimeFormatter.java)
+      // - j$.time.format.DateTimeFormatter.ofLocalizedDateTime (DateTimeFormatter.java)
+      DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
+      DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
+      DateTimeFormatter formatter2 =
+          DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT);
+      DateTimeFormatter formatter =
+          builder.append(formatter1).appendLiteral(" - ").append(formatter2).toFormatter();
+      LocalDateTime dateTime = LocalDateTime.of(1, 2, 3, 4, 5);
+      String str = dateTime.format(formatter);
+      System.out.println(str);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index 5aeaddc..9c4c113 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.GraphLens;
@@ -68,10 +69,13 @@
 
   @Test
   public void testEmptyDebugInfo() {
-    DexDebugInfo debugInfo = new DexDebugInfo(1, DexString.EMPTY_ARRAY, new DexDebugEvent[]{});
+    DexDebugInfo debugInfo =
+        new EventBasedDebugInfo(1, DexString.EMPTY_ARRAY, new DexDebugEvent[] {});
     DebugBytecodeWriter writer =
         new DebugBytecodeWriter(
-            debugInfo, emptyObjectTObjectMapping(), GraphLens.getIdentityLens());
+            DexDebugInfo.convertToWritable(debugInfo),
+            emptyObjectTObjectMapping(),
+            GraphLens.getIdentityLens());
     Assert.assertEquals(3, writer.generate().length);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java
index 8552e48..cc582ea 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java
@@ -158,29 +158,33 @@
   public void testPhiWithDifferentNewInstance() {
     buildAndCheckIR(
         "phiWithDifferentNewInstance",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(2, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, false);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
+        checkOptimizerStates(
+            appView,
+            optimizer -> {
+              assertEquals(0, optimizer.analysis.builderStates.size());
+              for (Value builder : optimizer.analysis.builderStates.keySet()) {
+                Map<Instruction, BuilderState> perBuilderState =
+                    optimizer.analysis.builderStates.get(builder);
+                checkBuilderState(optimizer, perBuilderState, null, false);
+              }
+              assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+            }));
   }
 
   @Test
   public void testPhiAtInit() {
     buildAndCheckIR(
         "phiAtInit",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(2, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, false);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
+        checkOptimizerStates(
+            appView,
+            optimizer -> {
+              assertEquals(0, optimizer.analysis.builderStates.size());
+              for (Value builder : optimizer.analysis.builderStates.keySet()) {
+                Map<Instruction, BuilderState> perBuilderState =
+                    optimizer.analysis.builderStates.get(builder);
+                checkBuilderState(optimizer, perBuilderState, null, false);
+              }
+              assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+            }));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index e18c8ea..2a81ef0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -265,7 +265,7 @@
 
   @Test
   public void testPhiAtInit() {
-    int expectedNumOfNewBuilder = 2;
+    int expectedNumOfNewBuilder = 0;
     boolean expectToMeetToString = false;
     if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.M)) {
       expectedNumOfNewBuilder = 1;
@@ -290,15 +290,17 @@
   public void testPhiWithDifferentInits() {
     buildAndCheckIR(
         "phiWithDifferentInits",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(2, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, false);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
+        checkOptimizerStates(
+            appView,
+            optimizer -> {
+              assertEquals(0, optimizer.analysis.builderStates.size());
+              for (Value builder : optimizer.analysis.builderStates.keySet()) {
+                Map<Instruction, BuilderState> perBuilderState =
+                    optimizer.analysis.builderStates.get(builder);
+                checkBuilderState(optimizer, perBuilderState, null, false);
+              }
+              assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+            }));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithLoopTest.java
new file mode 100644
index 0000000..8cc98c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithLoopTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StringBuilderWithLoopTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Utils.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1", "2");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      StringBuilder sb1 = new StringBuilder();
+      StringBuilder sb2 = new StringBuilder();
+      int i = 1;
+      String line;
+      while ((line = Utils.readLine(i)) != null) {
+        StringBuilder sb = i == 1 ? sb1 : sb2;
+        sb.append(line);
+        i++;
+      }
+      System.out.println(sb1.toString());
+      System.out.println(sb2.toString());
+    }
+  }
+
+  static class Utils {
+
+    // @Keep
+    static String readLine(int i) {
+      if (i <= 2) {
+        return Integer.toString(i);
+      }
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
index b98cc63..dd2700f 100644
--- a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
+++ b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -28,22 +29,29 @@
   private final Path originalJar;
   private final Path instrumentedJar;
 
-  // Create JacocoClasses with just one class provided as bytes.
+  // Create JacocoClasses with just one class provided as class file bytes.
   public JacocoClasses(byte[] clazz, TemporaryFolder temp) throws IOException {
+    this(ImmutableList.of(clazz), temp);
+  }
+
+  // Create JacocoClasses with multiple classes provided as class file bytes.
+  public JacocoClasses(List<byte[]> classes, TemporaryFolder temp) throws IOException {
     this.temp = temp;
     dir = temp.newFolder().toPath();
 
     // Write the class to a .class file with package sub-directories.
-    String typeName = TestBase.extractClassName(clazz);
-    int lastDotIndex = typeName.lastIndexOf('.');
-    String pkg = typeName.substring(0, lastDotIndex);
-    String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
     Path original = dir.resolve("original");
-    Files.createDirectories(original);
-    Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar));
-    Files.createDirectories(packageDir);
-    Path classFile = packageDir.resolve(baseFileName);
-    Files.write(classFile, clazz);
+    for (byte[] clazz : classes) {
+      String typeName = TestBase.extractClassName(clazz);
+      int lastDotIndex = typeName.lastIndexOf('.');
+      String pkg = typeName.substring(0, lastDotIndex);
+      String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
+      Files.createDirectories(original);
+      Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar));
+      Files.createDirectories(packageDir);
+      Path classFile = packageDir.resolve(baseFileName);
+      Files.write(classFile, clazz);
+    }
 
     // Run offline instrumentation.
     Path instrumented = dir.resolve("instrumented");
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index a6505c8..3531c36 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debuginfo.DebugInfoInspector;
 import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
@@ -24,65 +24,70 @@
   public void testSwap() throws Exception {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-    MethodSignature foo = clazz.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V",
-        // The first three vars are out-of-order to verify that the order is not relied on.
-        ".var 5 is t I from L4 to L6",
-        ".var 1 is bar Ljava/lang/String; from L0 to L9",
-        ".var 0 is this LTest; from L0 to L9",
-        ".var 2 is x I from L1 to L9",
-        ".var 3 is y I from L2 to L9",
-        ".var 4 is z I from L3 to L9",
-        ".var 5 is foobar Ljava/lang/String; from L7 to L9",
-        ".limit locals 6",
-        ".limit stack 2",
-        "L0:",
-        ".line 23",
-        " iconst_1",
-        " istore 2",
-        "L1:",
-        ".line 24",
-        " iconst_2",
-        " istore 3",
-        "L2:",
-        ".line 25",
-        " iconst_3",
-        " istore 4",
-        "L3:",
-        " .line 27",
-        " iload 3",
-        " istore 5",
-        "L4:",
-        " .line 28",
-        " iload 2",
-        " istore 3",
-        "L5:",
-        " .line 29",
-        " iload 5",
-        " istore 2",
-        "L6:",
-        " .line 32",
-        " new java/lang/StringBuilder",
-        " dup",
-        " invokespecial java/lang/StringBuilder/<init>()V",
-        " ldc \"And the value of y is: \"",
-        " invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;",
-        " iload 2",
-        " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;",
-        " iload 3",
-        " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;",
-        " iload 4",
-        " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;",
-        " invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;",
-        " astore 5",
-        "L7:",
-        " .line 34",
-        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "  aload 5",
-        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
-        "L8:",
-        " .line 35",
-        " return",
-        "L9:");
+    MethodSignature foo =
+        clazz.addVirtualMethod(
+            "foo",
+            ImmutableList.of("Ljava/lang/String;"),
+            "V",
+            // The first three vars are out-of-order to verify that the order is not relied on.
+            ".var 5 is t I from L4 to L6",
+            ".var 1 is bar Ljava/lang/String; from L0 to L9",
+            ".var 0 is this LTest; from L0 to L9",
+            ".var 2 is x I from L1 to L9",
+            ".var 3 is y I from L2 to L9",
+            ".var 4 is z I from L3 to L9",
+            ".var 5 is foobar Ljava/lang/String; from L7 to L9",
+            ".limit locals 6",
+            ".limit stack 2",
+            "L0:",
+            ".line 23",
+            " iconst_1",
+            " istore 2",
+            "L1:",
+            ".line 24",
+            " iconst_2",
+            " istore 3",
+            "L2:",
+            ".line 25",
+            " iconst_3",
+            " istore 4",
+            "L3:",
+            " .line 27",
+            " iload 3",
+            " istore 5",
+            "L4:",
+            " .line 28",
+            " iload 2",
+            " istore 3",
+            "L5:",
+            " .line 29",
+            " iload 5",
+            " istore 2",
+            "L6:",
+            " .line 32",
+            " new java/lang/StringBuilder",
+            " dup",
+            " invokespecial java/lang/StringBuilder/<init>()V",
+            " ldc \"And the value of y is: \"",
+            " invokevirtual"
+                + " java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+            " iload 2",
+            " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;",
+            " iload 3",
+            " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;",
+            " iload 4",
+            " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;",
+            " invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;",
+            " astore 5",
+            "L7:",
+            " .line 34",
+            "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+            "  aload 5",
+            "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+            "L8:",
+            " .line 35",
+            " return",
+            "L9:");
 
     clazz.addMainMethod(
         ".limit stack 3",
@@ -105,7 +110,7 @@
     ClassSubject classSubject = inspector.clazz("Test");
     MethodSubject methodSubject = classSubject.method(foo);
     DexCode code = methodSubject.getMethod().getCode().asDexCode();
-    DexDebugInfo info = code.getDebugInfo();
+    EventBasedDebugInfo info = code.getDebugInfo().asEventBasedInfo();
     assertEquals(23, info.startLine);
     assertEquals(1, info.parameters.length);
     assertEquals("bar", info.parameters[0].toString());
diff --git a/src/test/java/com/android/tools/r8/naming/FieldSharedParentMinificationTest.java b/src/test/java/com/android/tools/r8/naming/FieldSharedParentMinificationTest.java
new file mode 100644
index 0000000..e3a8047
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/FieldSharedParentMinificationTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldSharedParentMinificationTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(
+            I.class, J.class, A.class, B.class, C.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("true", "42", "Hello World!")
+        .inspect(
+            inspector -> {
+              FieldSubject foo = inspector.clazz(I.class).uniqueFieldWithName("foo");
+              FieldSubject bar = inspector.clazz(J.class).uniqueFieldWithName("bar");
+              FieldSubject baz = inspector.clazz(A.class).uniqueFieldWithName("baz");
+              assertThat(foo, isPresentAndRenamed());
+              assertThat(bar, isPresentAndRenamed());
+              assertThat(baz, isPresentAndRenamed());
+              Set<String> seenNames =
+                  ImmutableSet.of(foo.getFinalName(), bar.getFinalName(), baz.getFinalName());
+              assertEquals(ImmutableSet.of("a", "b", "c"), seenNames);
+            });
+  }
+
+  public interface I {
+
+    int foo = 42;
+  }
+
+  public interface J {
+    String bar = "Hello World!";
+  }
+
+  public static class A {
+
+    boolean baz = System.currentTimeMillis() > 0;
+  }
+
+  public static class B extends A implements I {}
+
+  public static class C extends A implements J {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A().baz);
+      System.out.println(new B().foo);
+      System.out.println(new C().bar);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
index ca294fa..f72ffa2 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
@@ -8,13 +8,27 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.testclasses.Greeting;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 /** Regression test for b/128600647. */
+@RunWith(Parameterized.class)
 public class InterfaceFieldMinificationTest extends TestBase {
 
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
   @Test
   public void test() throws Exception {
     String expectedOutput = StringUtils.lines("Greeter: Hello world!");
@@ -26,7 +40,8 @@
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
-        .run(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
   }
 
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index 5ed7de6..c09e451 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -138,7 +138,7 @@
     // as this is a release build.
     assertTrue(
         (code.getDebugInfo() == null)
-            || Arrays.stream(code.getDebugInfo().events)
+            || Arrays.stream(code.getDebugInfo().asEventBasedInfo().events)
                 .allMatch(event -> !(event instanceof StartLocal)));
   }
 
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
index d7133e7..fa3661e 100644
--- a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
@@ -35,7 +35,8 @@
   }
 
   private boolean hasLocal(MethodSubject method) {
-    return Arrays.stream(method.getMethod().getCode().asDexCode().getDebugInfo().events)
+    return Arrays.stream(
+            method.getMethod().getCode().asDexCode().getDebugInfo().asEventBasedInfo().events)
         .anyMatch(event -> event instanceof StartLocal);
   }
 
diff --git a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
index cc7900e..5405ecb 100644
--- a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.regress.b150400371;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -45,8 +45,9 @@
               MethodSubject main =
                   inspector.method(InlineInto.class.getDeclaredMethod("main", String[].class));
               if (parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) {
+                // Method has 14 actual lines, the PC mapping table will have about 50.
                 IntSet lines = new IntArraySet(main.getLineNumberTable().getLines());
-                assertEquals(2, lines.size());
+                assertTrue(lines.size() > 20);
               } else {
                 assertNull(main.getLineNumberTable());
               }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
index 153bf38..02947f7 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
@@ -8,14 +8,29 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.lang.reflect.Proxy;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class IfOnTargetedMethodTest extends TestBase {
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  @Parameter public TestParameters parameters;
+
   @Test
   public void test() throws Exception {
     String expectedOutput = StringUtils.lines("Hello world!");
@@ -23,13 +38,14 @@
     testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
 
     CodeInspector inspector =
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
             .addInnerClasses(IfOnTargetedMethodTest.class)
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
                 "-if interface * { @" + MyAnnotation.class.getTypeName() + " <methods>; }",
                 "-keep,allowobfuscation interface <1>")
-            .run(TestClass.class)
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 296004d..8644e9a 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -996,7 +996,7 @@
                           DescriptorUtils.getClassBinaryName(bootstrapMethodHolder),
                           bootstrapMethodName,
                           bootstrapMethodSignature,
-                          false),
+                          bootstrapMethodHolder.isInterface()),
                       new Object[] {}));
             } else {
               super.visitLdcInsn(value);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 813739b..a41ab17 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -217,16 +218,21 @@
     }
     if (code.isDexCode()) {
       DexCode dexCode = code.asDexCode();
-      if (dexCode.getDebugInfo() != null) {
-        for (DexString parameter : dexCode.getDebugInfo().parameters) {
-          if (parameter != null) {
-            return true;
-          }
+      if (dexCode.getDebugInfo() == null) {
+        return false;
+      }
+      EventBasedDebugInfo eventBasedInfo = dexCode.getDebugInfo().asEventBasedInfo();
+      if (eventBasedInfo == null) {
+        return false;
+      }
+      for (DexString parameter : eventBasedInfo.parameters) {
+        if (parameter != null) {
+          return true;
         }
-        for (DexDebugEvent event : dexCode.getDebugInfo().events) {
-          if (event instanceof DexDebugEvent.StartLocal) {
-            return true;
-          }
+      }
+      for (DexDebugEvent event : eventBasedInfo.events) {
+        if (event instanceof DexDebugEvent.StartLocal) {
+          return true;
         }
       }
       return false;
@@ -268,14 +274,14 @@
   }
 
   private LineNumberTable getDexLineNumberTable(DexCode code) {
-    DexDebugInfo debugInfo = code.getDebugInfo();
-    if (debugInfo == null) {
+    EventBasedDebugInfo info = DexDebugInfo.convertToEventBased(code, codeInspector.getFactory());
+    if (info == null) {
       return null;
     }
     Object2IntMap<InstructionSubject> lineNumberTable = new Object2IntOpenHashMap<>();
     DexDebugPositionState state =
-        new DexDebugPositionState(debugInfo.startLine, getMethod().getReference());
-    Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
+        new DexDebugPositionState(info.startLine, getMethod().getReference());
+    Iterator<DexDebugEvent> iterator = Arrays.asList(info.events).iterator();
     for (Instruction insn : code.instructions) {
       int offset = insn.getOffset();
       while (state.getCurrentPc() < offset && iterator.hasNext()) {