Introduce a PC based debug info subclass.

Bug: 205910335
Change-Id: I1c4513ee4db838cfeed84fc86caee4c92214e82f
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/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index f99eee5..39e491e 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;
     }
 
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..3f870f9 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 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.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -592,7 +593,7 @@
         }
       }
     }
-    return new DexDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY));
+    return 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..a98a415 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.code.SwitchPayload;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
 import com.android.tools.r8.graph.DexCode.TryHandler;
@@ -37,6 +38,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;
@@ -216,11 +218,13 @@
 
   private DexDebugInfo rewriteDebugInfoOffsets() {
     DexCode code = method.getCode().asDexCode();
-    if (debugEventTargets.size() != 0) {
+    if (!debugEventTargets.isEmpty()) {
+      assert code.getDebugInfo().isEventBasedInfo();
+      EventBasedDebugInfo eventBasedInfo = code.getDebugInfo().asEventBasedInfo();
       int lastOriginalOffset = 0;
       int lastNewOffset = 0;
       List<DexDebugEvent> events = new ArrayList<>();
-      for (DexDebugEvent event : code.getDebugInfo().events) {
+      for (DexDebugEvent event : eventBasedInfo.events) {
         if (event instanceof AdvancePC) {
           AdvancePC advance = (AdvancePC) event;
           lastOriginalOffset += advance.delta;
@@ -240,9 +244,9 @@
           events.add(event);
         }
       }
-      return new DexDebugInfo(
-          code.getDebugInfo().startLine,
-          code.getDebugInfo().parameters,
+      return new EventBasedDebugInfo(
+          eventBasedInfo.startLine,
+          eventBasedInfo.parameters,
           events.toArray(DexDebugEvent.EMPTY_ARRAY));
     }
     return code.getDebugInfo();
@@ -480,22 +484,31 @@
 
   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);
-        }
+    if (debugInfo == null) {
+      return;
+    }
+    if (debugInfo.isPcBasedInfo()) {
+      // TODO(b/205910335): merging pc based builds may require computing new pc mappings.
+      //  In these cases the old PC should be treated as a "line" and the translation must be
+      //  recorded and amended in the output. Reading PC based debug info as inputs is not yet
+      //  supported so this is unreachable.
+      throw new Unreachable();
+    }
+    EventBasedDebugInfo eventBasedInfo = debugInfo.asEventBasedInfo();
+    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/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 703977e..6cd9cfe 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;
@@ -230,15 +231,17 @@
 
   public DexDebugInfo debugInfoWithFakeThisParameter(DexItemFactory factory) {
     if (debugInfo == null) {
-      return null;
+      return debugInfo;
     }
+    assert debugInfo.isEventBasedInfo();
+    EventBasedDebugInfo eventBasedInfo = debugInfo.asEventBasedInfo();
     // 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,11 +249,11 @@
     }
 
     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
@@ -268,10 +271,12 @@
   private DexDebugInfo debugInfoAsInlining(DexMethod caller, DexMethod callee) {
     Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     if (debugInfo == null) {
+      // Inlining is not supported for PC based debug info. The conversion to PC should happen
+      // after optimization (or only for D8 merge inputs) so this should be unreachable.
       // 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[] {
@@ -279,7 +284,9 @@
             DexDebugEvent.ZERO_CHANGE_DEFAULT_EVENT
           });
     }
-    DexDebugEvent[] oldEvents = debugInfo.events;
+    assert debugInfo.isEventBasedInfo();
+    EventBasedDebugInfo eventBasedInfo = debugInfo.asEventBasedInfo();
+    DexDebugEvent[] oldEvents = eventBasedInfo.events;
     DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1];
     int i = 0;
     newEvents[i++] = new DexDebugEvent.SetInlineFrame(callee, callerPosition);
@@ -296,7 +303,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) {
@@ -313,15 +320,17 @@
 
   public DexDebugInfo debugInfoWithoutFirstParameter() {
     if (debugInfo == null) {
-      return null;
-    }
-    DexString[] parameters = debugInfo.parameters;
-    if(parameters.length == 0) {
       return debugInfo;
     }
+    assert debugInfo.isEventBasedInfo();
+    EventBasedDebugInfo eventBasedInfo = debugInfo.asEventBasedInfo();
+    DexString[] parameters = eventBasedInfo.parameters;
+    if(parameters.length == 0) {
+      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
@@ -566,18 +575,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;
   }
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/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..a72d269 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,321 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+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.Unimplemented;
+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.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.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);
+
+  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 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);
+      }
+      DexDebugEvent.ZERO_CHANGE_DEFAULT_EVENT.writeOn(writer, mapping, graphLens);
+      for (int i = 0; i < maxPc; i++) {
+        throw new Unimplemented("add 1,1 increment");
+      }
+      writer.putByte(Constants.DBG_END_SEQUENCE);
+    }
+
+    @Override
+    public String toString() {
+      return "PcBasedDebugInfo (params: " + parameterCount + ", max-pc: " + maxPc + ")";
+    }
+  }
+
+  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
+    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 DexDebugInfoForWriting convertToWritable(DexDebugInfo debugInfo) {
+    if (debugInfo == null) {
+      return null;
+    }
+    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);
   }
 
-  @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");
+  private static class WritableEventBasedDebugInfo extends EventBasedDebugInfo
+      implements DexDebugInfoForWriting {
+
+    private WritableEventBasedDebugInfo(
+        int startLine, DexString[] parameters, DexDebugEvent[] writableEvents) {
+      super(startLine, parameters, writableEvents);
     }
-    builder.append("  END_SEQUENCE\n");
-    builder.append("]\n");
-    return builder.toString();
+
+    @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..d056899 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();
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..c3c65fe 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
@@ -83,7 +83,9 @@
     this.originalMethod = originalMethod;
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
-      debugEntries = info.computeEntries(originalMethod);
+      // IR converting inputs with PC based debug info is not supported.
+      assert info.isEventBasedInfo();
+      debugEntries = info.asEventBasedInfo().computeEntries(originalMethod);
     }
     canonicalPositions =
         new CanonicalPositions(
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/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 71da5cc..b72dbae 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;
@@ -727,7 +728,9 @@
     }
     if (code.isDexCode()) {
       DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
-      return dexDebugInfo == null ? 0 : dexDebugInfo.startLine;
+      return dexDebugInfo == null || !dexDebugInfo.isEventBasedInfo()
+          ? 0
+          : dexDebugInfo.asEventBasedInfo().startLine;
     } else if (code.isCfCode()) {
       List<CfInstruction> instructions = code.asCfCode().getInstructions();
       for (CfInstruction instruction : instructions) {
@@ -823,7 +826,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 +857,9 @@
     // Do the actual processing for each method.
     DexApplication application = appView.appInfo().app();
     DexCode dexCode = method.getCode().asDexCode();
-    DexDebugInfo debugInfo = dexCode.getDebugInfo();
+    EventBasedDebugInfo debugInfo = dexCode.getDebugInfo().asEventBasedInfo();
+    // TODO(b/205910335): Reconsider this to support pc-based debug info from inputs.
+    assert debugInfo != null;
     List<DexDebugEvent> processedEvents = new ArrayList<>();
 
     PositionEventEmitter positionEventEmitter =
@@ -949,8 +957,8 @@
       return mappedPositions;
     }
 
-    DexDebugInfo optimizedDebugInfo =
-        new DexDebugInfo(
+    EventBasedDebugInfo optimizedDebugInfo =
+        new EventBasedDebugInfo(
             positionEventEmitter.getStartLine(),
             debugInfo.parameters,
             processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
@@ -989,7 +997,9 @@
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexCode dexCode = method.getCode().asDexCode();
-    DexDebugInfo debugInfo = dexCode.getDebugInfo();
+    EventBasedDebugInfo debugInfo = dexCode.getDebugInfo().asEventBasedInfo();
+    // TODO(b/205910335): Reconsider this to support pc-based debug info from inputs.
+    assert debugInfo != null;
 
     Pair<Integer, Position> lastPosition = new Pair<>();
 
@@ -1034,7 +1044,7 @@
   }
 
   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/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index 3ccf46d..c1fad9f 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) {
+    DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
+    if (debugInfo != null) {
+      assert debugInfo.isEventBasedInfo();
+      info = debugInfo.asEventBasedInfo();
       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/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/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/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/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/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 813739b..d276f5f 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;
@@ -272,10 +278,18 @@
     if (debugInfo == null) {
       return null;
     }
+    if (debugInfo.isPcBasedInfo()) {
+      // For testing we could either view PC based encoding as not having a line table
+      // or as having the full line table for each PC, or we could trim the table to the PC at
+      // each valid instruction. It is unclear which choice that makes most sense so this throws
+      // for now.
+      throw new Unimplemented();
+    }
+    EventBasedDebugInfo eventBasedInfo = debugInfo.asEventBasedInfo();
     Object2IntMap<InstructionSubject> lineNumberTable = new Object2IntOpenHashMap<>();
     DexDebugPositionState state =
-        new DexDebugPositionState(debugInfo.startLine, getMethod().getReference());
-    Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
+        new DexDebugPositionState(eventBasedInfo.startLine, getMethod().getReference());
+    Iterator<DexDebugEvent> iterator = Arrays.asList(eventBasedInfo.events).iterator();
     for (Instruction insn : code.instructions) {
       int offset = insn.getOffset();
       while (state.getCurrentPc() < offset && iterator.hasNext()) {