Encode SSA value references using relative index.

Bug: b//225838009
Change-Id: I6fa7c9042bd9bf8ab376477dcb311116357c5a6f
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
index 5c51959..f719f11 100644
--- a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
+++ b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
@@ -40,10 +40,10 @@
 
   public static int readEncodedInt(ByteIterator iterator) {
     assert 4 == intEncodingSize(0);
-    int value = ensureU1(iterator.nextByte()) << 24;
-    value |= ensureU1(iterator.nextByte()) << 16;
-    value |= ensureU1(iterator.nextByte()) << 8;
-    value |= ensureU1(iterator.nextByte());
+    int value = truncateToU1(iterator.nextByte()) << 24;
+    value |= truncateToU1(iterator.nextByte()) << 16;
+    value |= truncateToU1(iterator.nextByte()) << 8;
+    value |= truncateToU1(iterator.nextByte());
     return value;
   }
 
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 9bdc15a..b1ad627 100644
--- a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
@@ -65,7 +65,9 @@
   }
 
   private void computeInstructions() {
-    int currentInstructionIndex = 0;
+    // The IR instruction index corresponds to the LIR value index which includes arguments and
+    // all instructions.
+    int currentValueIndex = 0;
     BasicBlockIterator blockIt = irCode.listIterator();
     while (blockIt.hasNext()) {
       BasicBlock block = blockIt.next();
@@ -77,7 +79,7 @@
         for (Phi phi : block.getPhis()) {
           permuteOperands(phi.getOperands(), permutation, operands);
           builder.addPhi(phi.getType(), Arrays.asList(operands));
-          currentInstructionIndex++;
+          currentValueIndex++;
         }
       }
       if (block.hasCatchHandlers()) {
@@ -89,25 +91,27 @@
       }
       InstructionIterator it = block.iterator();
       while (it.hasNext()) {
+        assert builder.verifyCurrentValueIndex(currentValueIndex);
         Instruction instruction = it.next();
         assert !instruction.hasOutValue()
-            || currentInstructionIndex == values.getInt(instruction.outValue());
+            || currentValueIndex == values.getInt(instruction.outValue());
         builder.setCurrentPosition(instruction.getPosition());
         if (!instruction.getDebugValues().isEmpty()) {
-          builder.setDebugLocalEnds(currentInstructionIndex, instruction.getDebugValues());
+          builder.setDebugLocalEnds(currentValueIndex, instruction.getDebugValues());
         }
 
         if (instruction.isGoto()) {
           BasicBlock nextBlock = blockIt.peekNext();
           if (instruction.asGoto().getTarget() == nextBlock) {
             builder.addFallthrough();
-            currentInstructionIndex++;
+            currentValueIndex++;
             continue;
           }
         }
         instruction.buildLIR(builder);
-        currentInstructionIndex++;
+        currentValueIndex++;
       }
+      assert builder.verifyCurrentValueIndex(currentValueIndex);
     }
   }
 
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 28547c0..56fb836 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
@@ -93,6 +93,11 @@
       currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
     }
 
+    @Override
+    public int getCurrentValueIndex() {
+      return nextInstructionIndex + code.getArgumentCount();
+    }
+
     private void closeCurrentBlock() {
       currentBlock = null;
     }
@@ -154,6 +159,9 @@
         block.setFilled();
         blockList.add(block);
       }
+      for (int i = 0; i < peekNextInstructionIndex(); ++i) {
+        valueNumberGenerator.next();
+      }
       return new IRCode(
           appView.options(),
           method,
@@ -179,7 +187,7 @@
     public Value getValue(int index) {
       Value value = values[index];
       if (value == null) {
-        value = new Value(valueNumberGenerator.next(), TypeElement.getBottom(), null);
+        value = new Value(index, TypeElement.getBottom(), null);
         values[index] = value;
       }
       return value;
@@ -206,7 +214,7 @@
       DebugLocalInfo localInfo = code.getDebugLocalInfo(valueIndex);
       Value value = values[valueIndex];
       if (value == null) {
-        value = new Value(valueNumberGenerator.next(), type, localInfo);
+        value = new Value(valueIndex, type, localInfo);
         values[valueIndex] = value;
       } else {
         value.setType(type);
@@ -225,9 +233,7 @@
       //  uniform with instructions.
       advanceInstructionState();
       // Creating the phi implicitly adds it to currentBlock.
-      Phi phi =
-          new Phi(
-              valueNumberGenerator.next(), currentBlock, type, localInfo, RegisterReadType.NORMAL);
+      Phi phi = new Phi(valueIndex, currentBlock, type, localInfo, RegisterReadType.NORMAL);
       Value value = values[valueIndex];
       if (value != null) {
         // A fake ssa value has already been created, replace the users by the actual phi.
@@ -269,12 +275,11 @@
     private Argument addArgument(DexType type, int index) {
       // Arguments are not included in the "instructions" so this does not call "addInstruction"
       // which would otherwise advance the state.
-      Value dest = getValue(index);
-      dest.setType(type.toTypeElement(appView));
+      TypeElement typeElement = type.toTypeElement(appView);
       DebugLocalInfo localInfo = code.getDebugLocalInfo(index);
-      if (localInfo != null) {
-        dest.setLocalInfo(localInfo);
-      }
+      Value dest = new Value(index, typeElement, localInfo);
+      assert values[index] == null;
+      values[index] = dest;
       Argument argument = new Argument(dest, index, type.isBooleanType());
       assert currentBlock != null;
       assert currentPosition.isSyntheticPosition();
@@ -370,9 +375,9 @@
     }
 
     @Override
-    public void onArrayLength(int arrayIndex) {
+    public void onArrayLength(int arrayValueIndex) {
       Value dest = getOutValueForNextInstruction(TypeElement.getInt());
-      Value arrayValue = getValue(arrayIndex);
+      Value arrayValue = getValue(arrayValueIndex);
       addInstruction(new ArrayLength(dest, arrayValue));
     }
 
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 4f3a62e..9095c8b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -96,6 +96,11 @@
     flushedPosition = currentPosition;
   }
 
+  public boolean verifyCurrentValueIndex(int valueIndex) {
+    assert instructionCount + argumentCount == valueIndex;
+    return true;
+  }
+
   public DexType toDexType(TypeElement typeElement) {
     if (typeElement.isPrimitiveType()) {
       return typeElement.asPrimitiveType().toDexType(factory);
@@ -143,12 +148,16 @@
     return valueIndexGetter.getValueIndex(value);
   }
 
-  private int valueIndexSize(int index) {
-    return ByteUtils.intEncodingSize(index);
+  private int valueIndexSize(int valueIndex, int referencingInstructionIndex) {
+    int referencingValueIndex = referencingInstructionIndex + argumentCount;
+    int encodedValueIndex = LIRUtils.encodeValueIndex(valueIndex, referencingValueIndex);
+    return ByteUtils.intEncodingSize(encodedValueIndex);
   }
 
-  private void writeValueIndex(int index) {
-    ByteUtils.writeEncodedInt(index, writer::writeOperand);
+  private void writeValueIndex(int valueIndex, int referencingInstructionIndex) {
+    int referencingValueIndex = referencingInstructionIndex + argumentCount;
+    int encodedValueIndex = LIRUtils.encodeValueIndex(valueIndex, referencingValueIndex);
+    ByteUtils.writeEncodedInt(encodedValueIndex, writer::writeOperand);
   }
 
   private int getBlockIndex(B block) {
@@ -174,14 +183,14 @@
     return this;
   }
 
-  public LIRBuilder<V, B> setDebugLocalEnds(int instructionIndex, Set<V> endValues) {
+  public LIRBuilder<V, B> setDebugLocalEnds(int instructionValueIndex, Set<V> endValues) {
     int size = endValues.size();
     int[] indices = new int[size];
     Iterator<V> iterator = endValues.iterator();
     for (int i = 0; i < size; i++) {
       indices[i] = getValueIndex(iterator.next());
     }
-    debugLocalEnds.put(instructionIndex, indices);
+    debugLocalEnds.put(instructionValueIndex, indices);
     return this;
   }
 
@@ -192,12 +201,12 @@
     return this;
   }
 
-  private void advanceInstructionState() {
+  private int advanceInstructionState() {
     if (!currentPosition.equals(flushedPosition)) {
       setPositionIndex(instructionCount, currentPosition);
       flushedPosition = currentPosition;
     }
-    ++instructionCount;
+    return instructionCount++;
   }
 
   private LIRBuilder<V, B> addNoOperandInstruction(int opcode) {
@@ -217,7 +226,7 @@
 
   private LIRBuilder<V, B> addInstructionTemplate(int opcode, List<DexItem> items, List<V> values) {
     assert values.size() < MAX_VALUE_COUNT;
-    advanceInstructionState();
+    int instructionIndex = advanceInstructionState();
     int operandSize = 0;
     for (DexItem item : items) {
       operandSize += constantIndexSize(item);
@@ -225,7 +234,7 @@
     for (int i = 0; i < values.size(); i++) {
       V value = values.get(i);
       int valueIndex = getValueIndex(value);
-      operandSize += valueIndexSize(valueIndex);
+      operandSize += valueIndexSize(valueIndex, instructionIndex);
       valueIndexBuffer[i] = valueIndex;
     }
     writer.writeInstruction(opcode, operandSize);
@@ -233,7 +242,7 @@
       writeConstantIndex(item);
     }
     for (int i = 0; i < values.size(); i++) {
-      writeValueIndex(valueIndexBuffer[i]);
+      writeValueIndex(valueIndexBuffer[i], instructionIndex);
     }
     return this;
   }
@@ -243,7 +252,7 @@
   }
 
   public LIRBuilder<V, B> addConstInt(int value) {
-    if (0 <= value && value <= 5) {
+    if (-1 <= value && value <= 5) {
       addNoOperandInstruction(LIROpcodes.ICONST_0 + value);
     } else {
       advanceInstructionState();
@@ -258,18 +267,7 @@
       case OBJECT:
         return addConstNull();
       case INT:
-        {
-          int intValue = (int) value;
-          if (-1 <= intValue && intValue <= 5) {
-            int opcode = LIROpcodes.ICONST_0 + intValue;
-            return addNoOperandInstruction(opcode);
-          }
-          int opcode = LIROpcodes.ICONST;
-          int size = ByteUtils.intEncodingSize(intValue);
-          writer.writeInstruction(opcode, size);
-          ByteUtils.writeEncodedInt(intValue, writer::writeOperand);
-          return this;
-        }
+        return addConstInt((int) value);
       case FLOAT:
       case LONG:
       case DOUBLE:
@@ -372,11 +370,11 @@
     }
     int targetIndex = getBlockIndex(trueTarget);
     int valueIndex = getValueIndex(value);
-    int operandSize = blockIndexSize(targetIndex) + valueIndexSize(valueIndex);
-    advanceInstructionState();
+    int operandSize = blockIndexSize(targetIndex) + valueIndexSize(valueIndex, instructionCount);
+    int instructionIndex = advanceInstructionState();
     writer.writeInstruction(opcode, operandSize);
     writeBlockIndex(targetIndex);
-    writeValueIndex(valueIndex);
+    writeValueIndex(valueIndex, instructionIndex);
     return this;
   }
 
@@ -405,16 +403,18 @@
       default:
         throw new Unreachable("Unexpected if kind " + ifKind);
     }
+    int instructionIndex = advanceInstructionState();
     int targetIndex = getBlockIndex(trueTarget);
     int valueOneIndex = getValueIndex(inValues.get(0));
     int valueTwoIndex = getValueIndex(inValues.get(1));
     int operandSize =
-        blockIndexSize(targetIndex) + valueIndexSize(valueOneIndex) + valueIndexSize(valueTwoIndex);
-    advanceInstructionState();
+        blockIndexSize(targetIndex)
+            + valueIndexSize(valueOneIndex, instructionIndex)
+            + valueIndexSize(valueTwoIndex, instructionIndex);
     writer.writeInstruction(opcode, operandSize);
     writeBlockIndex(targetIndex);
-    writeValueIndex(valueOneIndex);
-    writeValueIndex(valueTwoIndex);
+    writeValueIndex(valueOneIndex, instructionIndex);
+    writeValueIndex(valueTwoIndex, instructionIndex);
     return this;
   }
 
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 09619f4..e39820a 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
@@ -12,10 +12,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.lightir.LIRBuilder.BlockIndexGetter;
 import com.android.tools.r8.lightir.LIRBuilder.ValueIndexGetter;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.StringUtils.BraceType;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import java.util.Arrays;
 
 public class LIRCode implements Iterable<LIRInstructionView> {
 
@@ -141,10 +138,10 @@
     return debugLocalInfoTable == null ? null : debugLocalInfoTable.valueToLocalMap.get(valueIndex);
   }
 
-  public int[] getDebugLocalEnds(int instructionIndex) {
+  public int[] getDebugLocalEnds(int instructionValueIndex) {
     return debugLocalInfoTable == null
         ? null
-        : debugLocalInfoTable.instructionToEndUseMap.get(instructionIndex);
+        : debugLocalInfoTable.instructionToEndUseMap.get(instructionValueIndex);
   }
 
   @Override
@@ -154,29 +151,6 @@
 
   @Override
   public String toString() {
-    StringBuilder builder = new StringBuilder("LIRCode{");
-    builder
-        .append("args:")
-        .append(argumentCount)
-        .append(", insn(num:")
-        .append(instructionCount)
-        .append(", size:")
-        .append(instructions.length)
-        .append("):{");
-    int index = 0;
-    for (LIRInstructionView view : this) {
-      builder.append(index).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("}, pool(size:").append(constants.length).append("):");
-    StringUtils.append(builder, Arrays.asList(constants), ", ", BraceType.TUBORG);
-    builder.append("}");
-    return builder.toString();
+    return new LIRPrinter(this).prettyPrint();
   }
 }
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 f0bc3f3..40395bb 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
@@ -25,7 +25,7 @@
  * <p>Due to the parsing of the individual instructions, this parser has a higher overhead than
  * using the basic {@code LIRInstructionView}.
  */
-public class LIRParsedInstructionCallback implements LIRInstructionCallback {
+public abstract class LIRParsedInstructionCallback implements LIRInstructionCallback {
 
   private final LIRCode code;
 
@@ -33,33 +33,70 @@
     this.code = code;
   }
 
-  public void onConstNull() {}
+  /** Returns the index for the value associated with the current argument/instruction. */
+  public abstract int getCurrentValueIndex();
 
-  public void onConstNumber(NumericType type, long value) {}
+  public final int getCurrentInstructionIndex() {
+    return getCurrentValueIndex() - code.getArgumentCount();
+  }
+
+  private int getActualValueIndex(int relativeValueIndex) {
+    return LIRUtils.decodeValueIndex(relativeValueIndex, getCurrentValueIndex());
+  }
+
+  private int getNextValueOperand(LIRInstructionView view) {
+    return getActualValueIndex(view.getNextValueOperand());
+  }
+
+  public void onInstruction() {}
+
+  public void onConstNull() {
+    onInstruction();
+  }
+
+  public void onConstNumber(NumericType type, long value) {
+    onInstruction();
+  }
 
   public void onConstInt(int value) {
     onConstNumber(NumericType.INT, value);
   }
 
-  public void onConstString(DexString string) {}
+  public void onConstString(DexString string) {
+    onInstruction();
+  }
 
-  public void onDiv(NumericType type, int leftValueIndex, int rightValueIndex) {}
+  public void onDiv(NumericType type, int leftValueIndex, int rightValueIndex) {
+    onInstruction();
+  }
 
   public void onDivInt(int leftValueIndex, int rightValueIndex) {
     onDiv(NumericType.INT, leftValueIndex, rightValueIndex);
   }
 
-  public void onIf(If.Type ifKind, int blockIndex, int valueIndex) {}
+  public void onIf(If.Type ifKind, int blockIndex, int valueIndex) {
+    onInstruction();
+  }
 
-  public void onGoto(int blockIndex) {}
+  public void onGoto(int blockIndex) {
+    onInstruction();
+  }
 
-  public void onFallthrough() {}
+  public void onFallthrough() {
+    onInstruction();
+  }
 
-  public void onMoveException(DexType exceptionType) {}
+  public void onMoveException(DexType exceptionType) {
+    onInstruction();
+  }
 
-  public void onDebugLocalWrite(int srcIndex) {}
+  public void onDebugLocalWrite(int srcIndex) {
+    onInstruction();
+  }
 
-  public void onInvokeMethodInstruction(DexMethod method, IntList arguments) {}
+  public void onInvokeMethodInstruction(DexMethod method, IntList arguments) {
+    onInstruction();
+  }
 
   public void onInvokeDirect(DexMethod method, IntList arguments) {
     onInvokeMethodInstruction(method, arguments);
@@ -70,27 +107,35 @@
   }
 
   public void onFieldInstruction(DexField field) {
-    onFieldInstruction(field);
+    onInstruction();
   }
 
   public void onStaticGet(DexField field) {
     onFieldInstruction(field);
   }
 
-  public void onReturnVoid() {}
+  public void onReturnVoid() {
+    onInstruction();
+  }
 
-  public void onArrayLength(int arrayIndex) {}
+  public void onArrayLength(int arrayValueIndex) {
+    onInstruction();
+  }
 
-  public void onDebugPosition() {}
+  public void onDebugPosition() {
+    onInstruction();
+  }
 
-  public void onPhi(DexType type, IntList operands) {}
+  public void onPhi(DexType type, IntList operands) {
+    onInstruction();
+  }
 
   private DexItem getConstantItem(int index) {
     return code.getConstantItem(index);
   }
 
   @Override
-  public final void onInstructionView(LIRInstructionView view) {
+  public void onInstructionView(LIRInstructionView view) {
     int opcode = view.getOpcode();
     switch (opcode) {
       case LIROpcodes.ACONST_NULL:
@@ -127,15 +172,15 @@
         }
       case LIROpcodes.IDIV:
         {
-          int leftValueIndex = view.getNextValueOperand();
-          int rightValueIndex = view.getNextValueOperand();
+          int leftValueIndex = getNextValueOperand(view);
+          int rightValueIndex = getNextValueOperand(view);
           onDivInt(leftValueIndex, rightValueIndex);
           return;
         }
       case LIROpcodes.IFNE:
         {
           int blockIndex = view.getNextBlockOperand();
-          int valueIndex = view.getNextValueOperand();
+          int valueIndex = getNextValueOperand(view);
           onIf(If.Type.NE, blockIndex, valueIndex);
           return;
         }
@@ -172,7 +217,7 @@
         }
       case LIROpcodes.ARRAYLENGTH:
         {
-          onArrayLength(view.getNextValueOperand());
+          onArrayLength(getNextValueOperand(view));
           return;
         }
       case LIROpcodes.DEBUGPOS:
@@ -185,7 +230,7 @@
           DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
           IntList operands = new IntArrayList();
           while (view.hasMoreOperands()) {
-            operands.add(view.getNextValueOperand());
+            operands.add(getNextValueOperand(view));
           }
           onPhi(type, operands);
           return;
@@ -203,7 +248,7 @@
         }
       case LIROpcodes.DEBUGLOCALWRITE:
         {
-          int srcIndex = view.getNextValueOperand();
+          int srcIndex = getNextValueOperand(view);
           onDebugLocalWrite(srcIndex);
           return;
         }
@@ -219,7 +264,7 @@
   private IntList getInvokeInstructionArguments(LIRInstructionView view) {
     IntList arguments = new IntArrayList();
     while (view.hasMoreOperands()) {
-      arguments.add(view.getNextValueOperand());
+      arguments.add(getNextValueOperand(view));
     }
     return arguments;
   }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRPrinter.java b/src/main/java/com/android/tools/r8/lightir/LIRPrinter.java
new file mode 100644
index 0000000..c8af439
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRPrinter.java
@@ -0,0 +1,202 @@
+// 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.lightir;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.utils.StringUtils;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+public class LIRPrinter extends LIRParsedInstructionCallback {
+
+  private static final String SEPERATOR = "\n";
+  private final LIRCode code;
+  private final StringBuilder builder = new StringBuilder();
+
+  private final int instructionIndexPadding;
+  private final int instructionNamePadding;
+
+  private int valueIndex = 0;
+  private LIRInstructionView view;
+
+  public LIRPrinter(LIRCode code) {
+    super(code);
+    this.code = code;
+    instructionIndexPadding =
+        Math.max(
+            fmtInsnIndex(-code.getArgumentCount()).length(),
+            fmtInsnIndex(code.getInstructionCount() - 1).length());
+    int maxNameLength = 0;
+    for (LIRInstructionView view : code) {
+      maxNameLength = Math.max(maxNameLength, LIROpcodes.toString(view.getOpcode()).length());
+    }
+    instructionNamePadding = maxNameLength;
+  }
+
+  @Override
+  public int getCurrentValueIndex() {
+    return valueIndex;
+  }
+
+  private void advanceToNextValueIndex() {
+    valueIndex++;
+  }
+
+  private String fmtValueIndex(int valueIndex) {
+    return "v" + valueIndex;
+  }
+
+  private String fmtInsnIndex(int instructionIndex) {
+    return instructionIndex < 0 ? "--" : ("" + instructionIndex);
+  }
+
+  private void appendValueArguments(IntList arguments) {
+    for (int i = 0; i < arguments.size(); i++) {
+      builder.append(fmtValueIndex(arguments.getInt(i))).append(' ');
+    }
+  }
+
+  public String prettyPrint() {
+    for (int i = 0; i < code.getArgumentCount(); i++) {
+      addInstructionHeader("ARG", 0);
+      appendOutValue();
+      advanceToNextValueIndex();
+    }
+    code.forEach(this::onInstructionView);
+    return builder.toString();
+  }
+
+  @Override
+  public void onInstructionView(LIRInstructionView view) {
+    this.view = view;
+    assert view.getInstructionIndex() == getCurrentInstructionIndex();
+    int operandSizeInBytes = view.getRemainingOperandSizeInBytes();
+    int instructionSizeInBytes = operandSizeInBytes == 0 ? 1 : 2 + operandSizeInBytes;
+    String opcode = LIROpcodes.toString(view.getOpcode());
+    addInstructionHeader(opcode, instructionSizeInBytes);
+    super.onInstructionView(view);
+    advanceToNextValueIndex();
+  }
+
+  private void addInstructionHeader(String opcode, int instructionSize) {
+    if (getCurrentValueIndex() > 0) {
+      builder.append(SEPERATOR);
+    }
+    StringUtils.appendLeftPadded(
+        builder, fmtInsnIndex(getCurrentInstructionIndex()), instructionIndexPadding);
+    builder.append(':');
+    StringUtils.appendLeftPadded(
+        builder, Integer.toString(instructionSize), instructionIndexPadding);
+    builder.append(": ");
+    StringUtils.appendRightPadded(builder, opcode, instructionNamePadding);
+    builder.append(' ');
+  }
+
+  @Override
+  public void onInstruction() {
+    throw new Unimplemented(
+        "Printing of instruction missing: " + LIROpcodes.toString(view.getOpcode()));
+  }
+
+  private StringBuilder appendOutValue() {
+    return builder.append(fmtValueIndex(getCurrentValueIndex())).append(" <- ");
+  }
+
+  @Override
+  public void onConstNull() {
+    appendOutValue().append("null");
+  }
+
+  @Override
+  public void onConstInt(int value) {
+    appendOutValue().append(value);
+  }
+
+  @Override
+  public void onConstString(DexString string) {
+    appendOutValue().append("str(").append(string).append(")");
+  }
+
+  @Override
+  public void onDiv(NumericType type, int leftValueIndex, int rightValueIndex) {
+    appendOutValue()
+        .append(fmtValueIndex(leftValueIndex))
+        .append(' ')
+        .append(fmtValueIndex(rightValueIndex))
+        .append(' ')
+        .append(type);
+  }
+
+  @Override
+  public void onIf(Type ifKind, int blockIndex, int valueIndex) {
+    builder.append(fmtValueIndex(valueIndex)).append(' ').append(fmtInsnIndex(blockIndex));
+  }
+
+  @Override
+  public void onGoto(int blockIndex) {
+    builder.append(fmtInsnIndex(blockIndex));
+  }
+
+  @Override
+  public void onFallthrough() {
+    // Nothing to append.
+  }
+
+  @Override
+  public void onMoveException(DexType exceptionType) {
+    appendOutValue().append(exceptionType);
+  }
+
+  @Override
+  public void onDebugLocalWrite(int srcIndex) {
+    appendOutValue().append(fmtValueIndex(srcIndex));
+  }
+
+  @Override
+  public void onInvokeMethodInstruction(DexMethod method, IntList arguments) {
+    if (!method.getReturnType().isVoidType()) {
+      appendOutValue();
+    }
+    appendValueArguments(arguments);
+    builder.append(method);
+  }
+
+  @Override
+  public void onFieldInstruction(DexField field) {
+    builder.append(field);
+  }
+
+  @Override
+  public void onStaticGet(DexField field) {
+    appendOutValue();
+    super.onStaticGet(field);
+  }
+
+  @Override
+  public void onReturnVoid() {
+    // Nothing to append.
+  }
+
+  @Override
+  public void onArrayLength(int arrayValueIndex) {
+    appendOutValue().append(fmtValueIndex(arrayValueIndex));
+  }
+
+  @Override
+  public void onDebugPosition() {
+    // Nothing to append.
+  }
+
+  @Override
+  public void onPhi(DexType type, IntList operands) {
+    appendOutValue();
+    appendValueArguments(operands);
+    builder.append(type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRUtils.java b/src/main/java/com/android/tools/r8/lightir/LIRUtils.java
new file mode 100644
index 0000000..09dcd81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRUtils.java
@@ -0,0 +1,17 @@
+// 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.lightir;
+
+public class LIRUtils {
+
+  private LIRUtils() {}
+
+  public static int encodeValueIndex(int absoluteValueIndex, int referencingValueContext) {
+    return referencingValueContext - absoluteValueIndex;
+  }
+
+  public static int decodeValueIndex(int encodedValueIndex, int referencingValueContext) {
+    return referencingValueContext - encodedValueIndex;
+  }
+}