Add position table to LIR.

Bug: b/225838009
Change-Id: I9f7d162a0abf667e69bc5de702e478d4aeae6abc
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index e5bdb6d..1f87da5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LIRBuilder;
 
 public class DebugPosition extends Instruction {
 
@@ -98,4 +99,9 @@
   public boolean isAllowedAfterThrowingInstruction() {
     return true;
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value> builder) {
+    builder.addDebugPosition(getPosition());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
index 6b56aa3..e91fd8a 100644
--- a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
@@ -26,7 +26,8 @@
       index++;
     }
     LIRBuilder<Value> builder =
-        new LIRBuilder<Value>(values::getInt).setMetadata(irCode.metadata());
+        new LIRBuilder<Value>(irCode.context().getReference(), values::getInt)
+            .setMetadata(irCode.metadata());
     BasicBlockIterator blockIt = irCode.listIterator();
     while (blockIt.hasNext()) {
       BasicBlock block = blockIt.next();
@@ -35,6 +36,7 @@
       InstructionIterator it = block.iterator();
       while (it.hasNext()) {
         Instruction instruction = it.next();
+        builder.setCurrentPosition(instruction.getPosition());
         instruction.buildLIR(builder);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
index 6d00eec..75a372b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
@@ -15,16 +15,19 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.lightir.LIRCode.PositionEntry;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -35,7 +38,7 @@
   private LIR2IRConverter() {}
 
   public static IRCode translate(ProgramMethod method, LIRCode lirCode, AppView<?> appView) {
-    Parser parser = new Parser(lirCode, appView);
+    Parser parser = new Parser(lirCode, method.getReference(), appView);
     parser.parseArguments(method);
     lirCode.forEach(view -> view.accept(parser));
     return parser.getIRCode(method);
@@ -55,14 +58,37 @@
     private final Value[] values;
     private final LinkedList<BasicBlock> blocks = new LinkedList<>();
 
-    private BasicBlock currentBlock;
+    private BasicBlock currentBlock = null;
     private int nextInstructionIndex = 0;
 
-    public Parser(LIRCode code, AppView<?> appView) {
+    private Position currentPosition;
+    private PositionEntry nextPositionEntry = null;
+    private int nextIndexInPositionsTable = 0;
+
+    public Parser(LIRCode code, DexMethod method, AppView<?> appView) {
       super(code);
+      assert code.getPositionTable().length > 0;
+      assert code.getPositionTable()[0].fromInstructionIndex == 0;
       this.appView = appView;
       this.code = code;
-      this.values = new Value[code.getArgumentCount() + code.getInstructionCount()];
+      values = new Value[code.getArgumentCount() + code.getInstructionCount()];
+      // Recreate the preamble position. This is active for arguments and code with no positions.
+      currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
+    }
+
+    private void ensureCurrentPosition() {
+      if (nextPositionEntry != null
+          && nextPositionEntry.fromInstructionIndex < nextInstructionIndex) {
+        currentPosition = nextPositionEntry.position;
+        advanceNextPositionEntry();
+      }
+    }
+
+    private void advanceNextPositionEntry() {
+      nextPositionEntry =
+          nextIndexInPositionsTable < code.getPositionTable().length
+              ? code.getPositionTable()[nextIndexInPositionsTable++]
+              : null;
     }
 
     public void parseArguments(ProgramMethod method) {
@@ -75,6 +101,8 @@
         addThisArgument(method.getHolderType());
       }
       method.getParameters().forEach(this::addArgument);
+      // Set up position state after adding arguments.
+      advanceNextPositionEntry();
     }
 
     public IRCode getIRCode(ProgramMethod method) {
@@ -132,8 +160,8 @@
     }
 
     private void addInstruction(Instruction instruction) {
-      // TODO(b/225838009): Add correct position info.
-      instruction.setPosition(Position.syntheticNone());
+      ensureCurrentPosition();
+      instruction.setPosition(currentPosition);
       currentBlock.getInstructions().add(instruction);
       instruction.setBlock(currentBlock);
       ++nextInstructionIndex;
@@ -200,5 +228,10 @@
     public void onReturnVoid() {
       addInstruction(new Return());
     }
+
+    @Override
+    public void onDebugPosition() {
+      addInstruction(new DebugPosition());
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
index 7d3505c..fe9e5b2 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -3,13 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.lightir.LIRCode.PositionEntry;
+import com.android.tools.r8.utils.ListUtils;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -28,13 +34,33 @@
   private final LIRWriter writer = new LIRWriter(byteWriter);
   private final Reference2IntMap<DexItem> constants;
   private final ValueIndexGetter<V> valueIndexGetter;
+  private final List<PositionEntry> positionTable;
   private int argumentCount = 0;
   private int instructionCount = 0;
   private IRMetadata metadata = null;
 
-  public LIRBuilder(ValueIndexGetter<V> valueIndexGetter) {
+  private Position currentPosition;
+  private Position flushedPosition;
+
+  public LIRBuilder(DexMethod method, ValueIndexGetter<V> valueIndexGetter) {
     constants = new Reference2IntOpenHashMap<>();
+    positionTable = new ArrayList<>();
     this.valueIndexGetter = valueIndexGetter;
+    currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
+    flushedPosition = currentPosition;
+  }
+
+  public LIRBuilder<V> setCurrentPosition(Position position) {
+    assert position != null;
+    assert position != Position.none();
+    currentPosition = position;
+    return this;
+  }
+
+  private void setPositionIndex(int instructionIndex, Position position) {
+    assert positionTable.isEmpty()
+        || ListUtils.last(positionTable).fromInstructionIndex < instructionIndex;
+    positionTable.add(new PositionEntry(instructionIndex, position));
   }
 
   private int getConstantIndex(DexItem item) {
@@ -82,20 +108,22 @@
     return this;
   }
 
-  public LIRBuilder<V> addNop() {
-    instructionCount++;
-    writer.writeOneByteInstruction(LIROpcodes.NOP);
-    return this;
+  private void addInstruction() {
+    if (!currentPosition.equals(flushedPosition)) {
+      setPositionIndex(instructionCount, currentPosition);
+      flushedPosition = currentPosition;
+    }
+    ++instructionCount;
   }
 
   public LIRBuilder<V> addConstNull() {
-    instructionCount++;
+    addInstruction();
     writer.writeOneByteInstruction(LIROpcodes.ACONST_NULL);
     return this;
   }
 
   public LIRBuilder<V> addConstInt(int value) {
-    instructionCount++;
+    addInstruction();
     if (0 <= value && value <= 5) {
       writer.writeOneByteInstruction(LIROpcodes.ICONST_0 + value);
     } else {
@@ -106,17 +134,17 @@
   }
 
   public LIRBuilder<V> addConstString(DexString string) {
-    instructionCount++;
+    addInstruction();
     return writeConstantReferencingInstruction(LIROpcodes.LDC, string);
   }
 
   public LIRBuilder<V> addStaticGet(DexField field) {
-    instructionCount++;
+    addInstruction();
     return writeConstantReferencingInstruction(LIROpcodes.GETSTATIC, field);
   }
 
   public LIRBuilder<V> addInvokeInstruction(int opcode, DexMethod method, List<V> arguments) {
-    instructionCount++;
+    addInstruction();
     int argumentOprandSize = constantIndexSize(method);
     int[] argumentIndexes = new int[arguments.size()];
     int i = 0;
@@ -142,12 +170,19 @@
   }
 
   public LIRBuilder<V> addReturn(V value) {
-    return this;
+    throw new Unimplemented();
   }
 
   public LIRBuilder<V> addReturnVoid() {
-    instructionCount++;
-    writer.writeInstruction(LIROpcodes.RETURN, 0);
+    addInstruction();
+    writer.writeOneByteInstruction(LIROpcodes.RETURN);
+    return this;
+  }
+
+  public LIRBuilder<V> addDebugPosition(Position position) {
+    assert currentPosition == position;
+    addInstruction();
+    writer.writeOneByteInstruction(LIROpcodes.DEBUGPOS);
     return this;
   }
 
@@ -157,6 +192,11 @@
     DexItem[] constantTable = new DexItem[constantsCount];
     constants.forEach((item, index) -> constantTable[index] = item);
     return new LIRCode(
-        metadata, constantTable, argumentCount, byteWriter.toByteArray(), instructionCount);
+        metadata,
+        constantTable,
+        positionTable.toArray(new PositionEntry[positionTable.size()]),
+        argumentCount,
+        byteWriter.toByteArray(),
+        instructionCount);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRCode.java b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
index d20c4d6..04dffd4 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
@@ -4,18 +4,33 @@
 package com.android.tools.r8.lightir;
 
 import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.lightir.LIRBuilder.ValueIndexGetter;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
 import java.util.Arrays;
 
 public class LIRCode implements Iterable<LIRInstructionView> {
 
+  public static class PositionEntry {
+    final int fromInstructionIndex;
+    final Position position;
+
+    public PositionEntry(int fromInstructionIndex, Position position) {
+      this.fromInstructionIndex = fromInstructionIndex;
+      this.position = position;
+    }
+  }
+
   private final IRMetadata metadata;
 
   /** Constant pool of items. */
   private final DexItem[] constants;
 
+  private final PositionEntry[] positionTable;
+
   /** Full number of arguments (including receiver for non-static methods). */
   private final int argumentCount;
 
@@ -25,19 +40,21 @@
   /** Cached value for the number of logical instructions (including phis). */
   private final int instructionCount;
 
-  public static <V> LIRBuilder<V> builder(ValueIndexGetter<V> valueIndexGetter) {
-    return new LIRBuilder<V>(valueIndexGetter);
+  public static <V> LIRBuilder<V> builder(DexMethod method, ValueIndexGetter<V> valueIndexGetter) {
+    return new LIRBuilder<V>(method, valueIndexGetter);
   }
 
   // Should be constructed using LIRBuilder.
   LIRCode(
       IRMetadata metadata,
       DexItem[] constants,
+      PositionEntry[] positions,
       int argumentCount,
       byte[] instructions,
       int instructionCount) {
     this.metadata = metadata;
     this.constants = constants;
+    this.positionTable = positions;
     this.argumentCount = argumentCount;
     this.instructions = instructions;
     this.instructionCount = instructionCount;
@@ -63,6 +80,10 @@
     return constants[index];
   }
 
+  public PositionEntry[] getPositionTable() {
+    return positionTable;
+  }
+
   @Override
   public LIRIterator iterator() {
     return new LIRIterator(new ByteArrayIterator(instructions));
@@ -71,26 +92,27 @@
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder("LIRCode{");
-    builder.append("constants:");
-    StringUtils.append(builder, Arrays.asList(constants));
     builder
-        .append(", arguments:")
+        .append("args:")
         .append(argumentCount)
-        .append(", instructions(size:")
+        .append(", insn(num:")
+        .append(instructionCount)
+        .append(", size:")
         .append(instructions.length)
         .append("):{");
     int index = 0;
     for (LIRInstructionView view : this) {
-      builder
-          .append(LIROpcodes.toString(view.getOpcode()))
-          .append("(size:")
-          .append(1 + view.getRemainingOperandSizeInBytes())
-          .append(")");
-      if (index++ < instructionCount) {
-        builder.append(",");
+      builder.append(LIROpcodes.toString(view.getOpcode()));
+      if (view.getRemainingOperandSizeInBytes() > 0) {
+        builder.append("(size:").append(1 + view.getRemainingOperandSizeInBytes()).append(")");
+      }
+      if (++index < instructionCount) {
+        builder.append(", ");
       }
     }
-    builder.append("}}");
+    builder.append("}, pool(size:").append(constants.length).append("):");
+    StringUtils.append(builder, Arrays.asList(constants), ", ", BraceType.TUBORG);
+    builder.append("}");
     return builder.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
index 6d906ed..089469f 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
@@ -13,12 +13,12 @@
 public interface LIROpcodes {
 
   static boolean isOneByteInstruction(int opcode) {
-    assert opcode >= NOP;
-    return opcode <= DCONST_1;
+    assert opcode >= ACONST_NULL;
+    return opcode <= DCONST_1 || opcode == RETURN || opcode == DEBUGPOS;
   }
 
   // Instructions maintaining the same opcode as defined in CF.
-  int NOP = 0;
+  // int NOP = 0;
   int ACONST_NULL = 1;
   int ICONST_M1 = 2;
   int ICONST_0 = 3;
@@ -182,11 +182,11 @@
   int FCONST = 202;
   int DCONST = 203;
   int INVOKEDIRECT = 204;
+  int DEBUGPOS = 205;
 
   static String toString(int opcode) {
     switch (opcode) {
-      case NOP:
-        return "NOP";
+        // case NOP: return "NOP";
       case ACONST_NULL:
         return "ACONST_NULL";
       case ICONST_M1:
@@ -485,6 +485,10 @@
         return "FCONST";
       case DCONST:
         return "DCONST";
+      case INVOKEDIRECT:
+        return "INVOKEDIRECT";
+      case DEBUGPOS:
+        return "DEBUGPOS";
 
       default:
         throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
index a0afc52..317eeee 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
@@ -54,6 +54,8 @@
 
   public void onReturnVoid() {}
 
+  public void onDebugPosition() {}
+
   private DexItem getConstantItem(int index) {
     return code.getConstantItem(index);
   }
@@ -99,6 +101,11 @@
           onReturnVoid();
           break;
         }
+      case LIROpcodes.DEBUGPOS:
+        {
+          onDebugPosition();
+          break;
+        }
       default:
         throw new Unimplemented("No dispatch for opcode " + LIROpcodes.toString(view.getOpcode()));
     }
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
index 302f82f..1273bfd 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
@@ -12,7 +12,10 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.references.Reference;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,8 +34,11 @@
 
   @Test
   public void test() throws Exception {
+    DexItemFactory factory = new DexItemFactory();
+    DexMethod method = factory.createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V"));
     LIRCode code =
         LIRCode.builder(
+                method,
                 v -> {
                   throw new Unreachable();
                 })
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
index 82c0d55..a169bfc 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
@@ -8,6 +8,7 @@
 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 org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,6 +46,22 @@
   public void testRoundtrip() throws Exception {
     testForD8(parameters.getBackend())
         .release()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(TestClass.class)
+        .addOptionsModification(
+            o -> {
+              o.testing.forceIRForCfToCfDesugar = true;
+              o.testing.roundtripThroughLIR = true;
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testRoundtripDebug() throws Exception {
+    testForD8(parameters.getBackend())
+        .debug()
+        .setMinApi(AndroidApiLevel.B)
         .addProgramClasses(TestClass.class)
         .addOptionsModification(
             o -> {