Remove debug positions subsumed by constant instructions.

Bug: 156591935
Bug: 166074600
Change-Id: Icc2ad58bd628976ca5b74425ab44cf94f44eabdc
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index b287f84..1334a46 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -86,6 +86,12 @@
     this.position = position;
   }
 
+  public void forceOverwritePosition(Position position) {
+    assert position != null;
+    assert this.position != null;
+    this.position = position;
+  }
+
   public String getPositionAsString() {
     return position == null ? "???" : position.toString();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
index 8eaf454..fc48072 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
@@ -19,7 +19,8 @@
 public class BasicBlockMuncher {
 
   private static List<BasicBlockPeephole> nonDestructivePeepholes() {
-    return ImmutableList.of(new MoveLoadUpPeephole(), new StoreLoadPeephole());
+    return ImmutableList.of(
+        new RemoveDebugPositionPeephole(), new MoveLoadUpPeephole(), new StoreLoadPeephole());
   }
 
   // The StoreLoadPeephole and StoreSequenceLoadPeephole are non-destructive but we would like it
@@ -48,13 +49,7 @@
           new LinearFlowInstructionListIterator(
               code, currentBlock, currentBlock.getInstructions().size());
       boolean matched = false;
-      while (matched || it.hasPrevious()) {
-        if (!it.hasPrevious()) {
-          matched = false;
-          it =
-              new LinearFlowInstructionListIterator(
-                  code, currentBlock, currentBlock.getInstructions().size());
-        }
+      while (true) {
         for (BasicBlockPeephole peepHole : peepholes) {
           boolean localMatch = peepHole.match(it);
           if (localMatch && peepHole.resetAfterMatch()) {
@@ -73,6 +68,13 @@
             iterations++;
           }
           it.previous();
+        } else if (matched) {
+          matched = false;
+          it =
+              new LinearFlowInstructionListIterator(
+                  code, currentBlock, currentBlock.getInstructions().size());
+        } else {
+          break;
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
index d656455..5fef766 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
@@ -20,10 +20,12 @@
             && (t.outValue() == null || !t.outValue().hasLocalInfo());
   }
 
-  public static void resetNext(InstructionListIterator it, int count) {
+  public static Instruction resetNext(InstructionListIterator it, int count) {
+    Instruction instruction = null;
     for (int i = 0; i < count; i++) {
-      it.previous();
+      instruction = it.previous();
     }
+    return instruction;
   }
 
   public static void resetPrevious(InstructionListIterator it, int count) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/RemoveDebugPositionPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/RemoveDebugPositionPeephole.java
new file mode 100644
index 0000000..99128d0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/RemoveDebugPositionPeephole.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.peepholes;
+
+import com.android.tools.r8.ir.code.DebugPosition;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Position;
+
+/**
+ * {@link RemoveDebugPositionPeephole} looks for the following two patterns:
+ *
+ * <pre>
+ *   p: DebugPosition
+ *   [q: Const]*
+ *   q: Instr // TODO(b/166074600): This must currently be a Const.
+ * </pre>
+ *
+ * if p = q:
+ *
+ * <pre>
+ *   [q: Const]*
+ *   q: Instr
+ * </pre>
+ *
+ * if p != q and size([q: Const]*) > 0:
+ *
+ * <pre>
+ *   [p: Const]*
+ *   q: Instr
+ * </pre>
+ *
+ * This rewrite will eliminate debug positions that can be placed on a constant, retaining the line
+ * but avoiding the nop resulting in a remaining position instruction.
+ */
+public class RemoveDebugPositionPeephole implements BasicBlockPeephole {
+
+  private final Point debugPositionExp = new Point(Instruction::isDebugPosition);
+  private final Point secondInstructionExp =
+      new Point(
+          // TODO(b/166074600): It should be possible to match on any materializing instruction
+          //  here. The phi-optimization seems to invalidate that by changing stack operations.
+          Instruction::isConstInstruction);
+
+  private final PeepholeLayout layout =
+      PeepholeLayout.lookForward(debugPositionExp, secondInstructionExp);
+
+  @Override
+  public boolean match(InstructionListIterator it) {
+    Match match = layout.test(it);
+    if (match == null) {
+      return false;
+    }
+    DebugPosition debugPosition = debugPositionExp.get(match).asDebugPosition();
+    Instruction secondInstruction = secondInstructionExp.get(match);
+
+    // If the position is the same on the following instruction it can simply be removed.
+    Position position = debugPosition.getPosition();
+    if (position.equals(secondInstruction.getPosition())) {
+      it.removeOrReplaceByDebugLocalRead();
+      return true;
+    }
+
+    boolean movedPosition = false;
+    it.next(); // skip debug position.
+    Instruction current = it.next(); // start loop at second instruction.
+    assert current == secondInstruction;
+    while (current.isConstInstruction() && it.hasNext()) {
+      Instruction next = it.next();
+      if (!next.getPosition().equals(current.getPosition())) {
+        break;
+      }
+      // The constant shares position with the next instruction so it subsumes the position of
+      // the debug position.
+      movedPosition = true;
+      current.forceOverwritePosition(position);
+      current = next;
+    }
+    it.previousUntil(i -> i == debugPosition);
+    if (movedPosition) {
+      it.next();
+      it.removeOrReplaceByDebugLocalRead();
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public boolean resetAfterMatch() {
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 8c6b864..7b630c3 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -192,7 +192,7 @@
   }
 
   public TestParametersCollection build() {
-    assert !enableApiLevels || hasDexRuntimeFilter;
+    assert !enableApiLevels || enableApiLevelsForCf || hasDexRuntimeFilter;
     return new TestParametersCollection(
         getAvailableRuntimes()
             .flatMap(this::createParameters)
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
index 63f1aa6..674f288 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
@@ -4,9 +4,13 @@
 
 package com.android.tools.r8.desugar;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -24,31 +28,87 @@
 public class DesugarToClassFileB156591935 extends TestBase implements Opcodes {
 
   @Parameterized.Parameters(name = "{0}")
-  public static AndroidApiLevel[] data() {
-    return AndroidApiLevel.values();
+  public static TestParametersCollection data() {
+    return TestParametersBuilder.builder().withCfRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   private final AndroidApiLevel apiLevel;
 
-  public DesugarToClassFileB156591935(AndroidApiLevel apiLevel) {
-    this.apiLevel = apiLevel;
+  public DesugarToClassFileB156591935(TestParameters parameters) {
+    this.apiLevel = parameters.getApiLevel();
   }
 
-  private void expectNops(CodeInspector inspector, int numberOfNops) {
-    ClassSubject a = inspector.clazz("A");
-    assertEquals(
-        numberOfNops, a.clinit().streamInstructions().filter(InstructionSubject::isNop).count());
+  private void expectNoNops(String className, CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(className);
+    assertFalse(classSubject.clinit().streamInstructions().anyMatch(InstructionSubject::isNop));
+  }
+
+  private void expectNoLoad(String className, CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(className);
+    assertFalse(
+        classSubject
+            .clinit()
+            .streamInstructions()
+            .anyMatch(instructionSubject -> instructionSubject.asCfInstruction().isLoad()));
+  }
+
+  private void expectNoStore(String className, CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(className);
+    assertFalse(
+        classSubject
+            .clinit()
+            .streamInstructions()
+            .anyMatch(instructionSubject -> instructionSubject.asCfInstruction().isStore()));
+  }
+
+  private void expectNoSwap(String className, CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(className);
+    assertFalse(
+        classSubject
+            .clinit()
+            .streamInstructions()
+            .anyMatch(
+                instructionSubject ->
+                    instructionSubject.asCfInstruction().isStackInstruction(Opcode.Swap)));
   }
 
   @Test
   public void test() throws Exception {
     // No nops in the input - see dump below.
-    // TODO(b/156591935): The three nops should be avoided.
     testForD8(Backend.CF)
         .addProgramClassFileData(dump())
         .setMinApi(apiLevel)
         .compile()
-        .inspect(inspector -> expectNops(inspector, 3));
+        .inspect(inspector -> expectNoNops("A", inspector))
+        .inspect(inspector -> expectNoLoad("A", inspector))
+        .inspect(inspector -> expectNoStore("A", inspector))
+        .inspect(inspector -> expectNoSwap("A", inspector));
+  }
+
+  @Test
+  public void testWithExtraLine() throws Exception {
+    // No nops in the input - see dump below.
+    testForD8(Backend.CF)
+        .addProgramClassFileData(dumpWithExtraLine())
+        .setMinApi(apiLevel)
+        .compile()
+        .inspect(inspector -> expectNoNops("A", inspector))
+        .inspect(inspector -> expectNoLoad("A", inspector))
+        .inspect(inspector -> expectNoStore("A", inspector))
+        .inspect(inspector -> expectNoSwap("A", inspector));
+  }
+
+  @Test
+  public void testWithMoreConsts() throws Exception {
+    // No nops in the input - see dump below.
+    testForD8(Backend.CF)
+        .addProgramClassFileData(dumpMoreConsts())
+        .setMinApi(apiLevel)
+        .compile()
+        .inspect(inspector -> expectNoNops("B", inspector))
+        .inspect(inspector -> expectNoLoad("B", inspector))
+        .inspect(inspector -> expectNoStore("B", inspector))
+        .inspect(inspector -> expectNoSwap("B", inspector));
   }
 
   /*
@@ -86,7 +146,7 @@
       }
     }
   */
-  public static byte[] dump() {
+  private static byte[] dump() {
 
     ClassWriter classWriter = new ClassWriter(0);
     FieldVisitor fieldVisitor;
@@ -251,4 +311,460 @@
 
     return classWriter.toByteArray();
   }
+
+  // Patched version of the code above. Added an additional line change between the two const
+  // instructions for the "createA" calls in <clinit>. Look for label variable names ending in x.
+  private static byte[] dumpWithExtraLine() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+    classWriter.visitSource("A.java", null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_1", "LA;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_2", "LA;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_3", "LA;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value", "I", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "name", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PRIVATE | ACC_STATIC, "createA", "(ILjava/lang/String;)LA;", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(69, label0);
+      methodVisitor.visitTypeInsn(NEW, "A");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "(ILjava/lang/String;)V", false);
+      methodVisitor.visitInsn(ARETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("value", "I", null, label0, label1, 0);
+      methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 1);
+      methodVisitor.visitMaxs(4, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PRIVATE, "<init>", "(ILjava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(72, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(73, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitFieldInsn(PUTFIELD, "A", "value", "I");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(74, label2);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 2);
+      methodVisitor.visitFieldInsn(PUTFIELD, "A", "name", "Ljava/lang/String;");
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(75, label3);
+      methodVisitor.visitInsn(RETURN);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLocalVariable("this", "LA;", null, label0, label4, 0);
+      methodVisitor.visitLocalVariable("value", "I", null, label0, label4, 1);
+      methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label4, 2);
+      methodVisitor.visitMaxs(2, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getValue", "()I", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(78, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "A", "value", "I");
+      methodVisitor.visitInsn(IRETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getName", "()Ljava/lang/String;", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(82, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "A", "name", "Ljava/lang/String;");
+      methodVisitor.visitInsn(ARETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(67, label0);
+      methodVisitor.visitInsn(ICONST_1);
+      Label label0x = new Label();
+      methodVisitor.visitLabel(label0x);
+      methodVisitor.visitLineNumber(68, label0x);
+      methodVisitor.visitLdcInsn("FIRST");
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(69, label1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_1", "LA;");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(70, label2);
+      methodVisitor.visitInsn(ICONST_1);
+      Label label2x = new Label();
+      methodVisitor.visitLabel(label2x);
+      methodVisitor.visitLineNumber(71, label2x);
+      methodVisitor.visitLdcInsn("SECOND");
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(72, label3);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_2", "LA;");
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(73, label4);
+      methodVisitor.visitInsn(ICONST_1);
+      Label label4x = new Label();
+      methodVisitor.visitLabel(label4x);
+      methodVisitor.visitLineNumber(74, label4x);
+      methodVisitor.visitLdcInsn("THIRD");
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(75, label5);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_3", "LA;");
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLineNumber(74, label6);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  /*
+    Dump of the compiled code for ths class below. The dump has not been modified, but the
+    code needs to have the specific line breaks for javac to insert the line numbers in the
+    way that this test is about. Used a dump to avoid source formatting invalidating the test.
+
+    This is the same pattern like class A above, just with more constants attributed to the
+    same line.
+
+    class B {
+      // The line break before createA is needed for the expected line info.
+      public static final B B_1 =
+          createB(1, 2, 3, "FIRST");
+      public static final B B_2 =
+          createB(1, 2, 3, "SECOND");
+      public static final B B_3 =
+          createB(1, 2, 3, "THIRD");
+
+      private final int value1;
+      private final int value2;
+      private final int value3;
+      private final String name;
+
+      private static B createB(int value1, int value2, int value3, String name) {
+        return new B(value1, value2, value3, name);
+      }
+
+      private B(int value1, int value2, int value3, String name) {
+        this.value1 = value1;
+        this.value2 = value2;
+        this.value3 = value3;
+        this.name = name;
+      }
+
+      int getValue1() {
+        return value1;
+      }
+
+      int getVlaue2() {
+        return value2;
+      }
+
+      int getValue3() {
+        return value3;
+      }
+
+      String getName() {
+        return name;
+      }
+    }
+  */
+  public static byte[] dumpMoreConsts() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V11, ACC_SUPER, "B", null, "java/lang/Object", null);
+
+    classWriter.visitSource("B.java", null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "B_1", "LB;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "B_2", "LB;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "B_3", "LB;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value1", "I", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value2", "I", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value3", "I", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "name", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PRIVATE | ACC_STATIC, "createB", "(IIILjava/lang/String;)LB;", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(16, label0);
+      methodVisitor.visitTypeInsn(NEW, "B");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitVarInsn(ALOAD, 3);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "B", "<init>", "(IIILjava/lang/String;)V", false);
+      methodVisitor.visitInsn(ARETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("value1", "I", null, label0, label1, 0);
+      methodVisitor.visitLocalVariable("value2", "I", null, label0, label1, 1);
+      methodVisitor.visitLocalVariable("value3", "I", null, label0, label1, 2);
+      methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 3);
+      methodVisitor.visitMaxs(6, 4);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PRIVATE, "<init>", "(IIILjava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(19, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(20, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitFieldInsn(PUTFIELD, "B", "value1", "I");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(21, label2);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitFieldInsn(PUTFIELD, "B", "value2", "I");
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(22, label3);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 3);
+      methodVisitor.visitFieldInsn(PUTFIELD, "B", "value3", "I");
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(23, label4);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 4);
+      methodVisitor.visitFieldInsn(PUTFIELD, "B", "name", "Ljava/lang/String;");
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(24, label5);
+      methodVisitor.visitInsn(RETURN);
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLocalVariable("this", "LB;", null, label0, label6, 0);
+      methodVisitor.visitLocalVariable("value1", "I", null, label0, label6, 1);
+      methodVisitor.visitLocalVariable("value2", "I", null, label0, label6, 2);
+      methodVisitor.visitLocalVariable("value3", "I", null, label0, label6, 3);
+      methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label6, 4);
+      methodVisitor.visitMaxs(2, 5);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getValue1", "()I", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(27, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "B", "value1", "I");
+      methodVisitor.visitInsn(IRETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getVlaue2", "()I", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(31, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "B", "value2", "I");
+      methodVisitor.visitInsn(IRETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getValue3", "()I", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(35, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "B", "value3", "I");
+      methodVisitor.visitInsn(IRETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getName", "()Ljava/lang/String;", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(39, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "B", "name", "Ljava/lang/String;");
+      methodVisitor.visitInsn(ARETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(ICONST_3);
+      methodVisitor.visitLdcInsn("FIRST");
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "B", "createB", "(IIILjava/lang/String;)LB;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "B", "B_1", "LB;");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(5, label2);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(ICONST_3);
+      methodVisitor.visitLdcInsn("SECOND");
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(6, label3);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "B", "createB", "(IIILjava/lang/String;)LB;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "B", "B_2", "LB;");
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(7, label4);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(ICONST_3);
+      methodVisitor.visitLdcInsn("THIRD");
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(8, label5);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "B", "createB", "(IIILjava/lang/String;)LB;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "B", "B_3", "LB;");
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLineNumber(7, label6);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(4, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 2387655..c1e8c90 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.graph.DexField;
@@ -55,6 +56,11 @@
   }
 
   @Override
+  public CfInstructionSubject asCfInstruction() {
+    return this;
+  }
+
+  @Override
   public boolean isFieldAccess() {
     return instruction instanceof CfFieldInstruction;
   }
@@ -297,6 +303,10 @@
     return instruction instanceof CfLoad;
   }
 
+  public boolean isStore() {
+    return instruction instanceof CfStore;
+  }
+
   @Override
   public boolean isMultiplication() {
     if (!(instruction instanceof CfArithmeticBinop)) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 0335a5d..26944f9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -121,6 +121,11 @@
   }
 
   @Override
+  public CfInstructionSubject asCfInstruction() {
+    return null;
+  }
+
+  @Override
   public boolean isFieldAccess() {
     return isInstanceGet() || isInstancePut() || isStaticGet() || isStaticPut();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index ad9b5c2..513de02 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -18,6 +18,8 @@
 
   DexInstructionSubject asDexInstruction();
 
+  CfInstructionSubject asCfInstruction();
+
   boolean isFieldAccess();
 
   boolean isInstancePut();