Ensure instruction iterators update IR metadata

Change-Id: Iecf7791970eb2e9d1565ae1c05d215c5a417ebff
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 59aae89..ec6710b 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -156,15 +156,14 @@
     // locals alive for their entire live range. In release mode the liveness is all that matters
     // and we do not actually want locals information in the output.
     if (appView.options().debug) {
-      LinearScanRegisterAllocator.computeDebugInfo(blocks, liveIntervals, this, liveAtEntrySets);
+      LinearScanRegisterAllocator.computeDebugInfo(
+          code, blocks, liveIntervals, this, liveAtEntrySets);
     }
   }
 
   private void computeNeedsRegister() {
-    InstructionIterator it = code.instructionIterator();
-    while (it.hasNext()) {
-      Instruction next = it.next();
-      Value outValue = next.outValue();
+    for (Instruction instruction : code.instructions()) {
+      Value outValue = instruction.outValue();
       if (outValue != null) {
         boolean isStackValue =
             (outValue instanceof StackValue) || (outValue instanceof StackValues);
@@ -505,10 +504,10 @@
 
   private void applyInstructionsBackwardsToRegisterLiveness(
       BasicBlock block, IntSet liveRegisters, int suffixSize) {
-    Iterator<Instruction> iterator = block.getInstructions().descendingIterator();
+    InstructionIterator iterator = block.iterator(block.getInstructions().size());
     int instructionsLeft = suffixSize;
-    while (--instructionsLeft >= 0 && iterator.hasNext()) {
-      Instruction current = iterator.next();
+    while (--instructionsLeft >= 0 && iterator.hasPrevious()) {
+      Instruction current = iterator.previous();
       Value outValue = current.outValue();
       if (outValue != null && outValue.needsRegister()) {
         int register = getRegisterForValue(outValue);
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 70f5ade..339f961 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -99,7 +99,7 @@
     clonableConstants = new IdentityHashMap<>();
     blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
-      InstructionListIterator it = blockIterator.next().listIterator();
+      InstructionListIterator it = blockIterator.next().listIterator(code);
       while (it.hasNext()) {
         it.next().insertLoadAndStores(it, this);
       }
@@ -128,7 +128,7 @@
               moves.add(new PhiMove(phi, value));
             }
           }
-          InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
+          InstructionListIterator it = pred.listIterator(code, pred.getInstructions().size());
           Instruction exit = it.previous();
           assert pred.exit() == exit;
           movePhis(moves, it, exit.getPosition());
@@ -201,7 +201,7 @@
     boolean hasCatchHandlers = instruction.getBlock().hasCatchHandlers();
     if (hasCatchHandlers && instruction.instructionTypeCanThrow()) {
       storeBlock = it.split(this.code, this.blockIterator);
-      it = storeBlock.listIterator();
+      it = storeBlock.listIterator(code);
     }
     add(store, storeBlock, instruction.getPosition(), it);
     if (hasCatchHandlers && !instruction.instructionTypeCanThrow()) {
@@ -223,7 +223,7 @@
     BasicBlock insertBlock = instruction.getBlock();
     if (insertBlock.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
       insertBlock = it.split(this.code, this.blockIterator);
-      it = insertBlock.listIterator();
+      it = insertBlock.listIterator(code);
     }
     instruction.swapOutValue(newOutValue);
     add(new Pop(newOutValue), insertBlock, instruction.getPosition(), it);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 83ce19e..b8834e5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -38,7 +39,6 @@
 import com.google.common.collect.Streams;
 import java.util.ArrayDeque;
 import java.util.Deque;
-import java.util.Iterator;
 import java.util.Set;
 
 /**
@@ -119,7 +119,7 @@
     // Visit all the instructions in all the blocks that dominate `block`.
     for (BasicBlock dominator : dominatorTree.dominatorBlocks(block, Inclusive.NO)) {
       AnalysisAssumption assumption = getAssumptionForDominator(dominator, block);
-      Iterator<Instruction> instructionIterator = dominator.iterator();
+      InstructionIterator instructionIterator = dominator.iterator();
       while (instructionIterator.hasNext()) {
         Instruction previous = instructionIterator.next();
         if (previous.definitelyTriggersClassInitialization(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 61ecf71..01a7e3d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Throw;
@@ -38,9 +37,7 @@
   }
 
   public boolean check(IRCode code) {
-    InstructionIterator instructionIterator = code.instructionIterator();
-    while (instructionIterator.hasNext()) {
-      Instruction instruction = instructionIterator.next();
+    for (Instruction instruction : code.instructions()) {
       if (instruction.isInstancePut()) {
         if (!check(instruction.asInstancePut())) {
           return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index 775553b..79081ee 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -80,36 +80,38 @@
     List<BasicBlock> blockToAnalyze = new ArrayList<>();
 
     mapping.entrySet().stream()
-        .filter((entry) -> entry.getValue().isConst())
-        .forEach((entry) -> {
-          Value value = entry.getKey();
-          ConstNumber evaluatedConst = entry.getValue().asConst().getConstNumber();
-          if (value.definition != evaluatedConst) {
-            if (value.isPhi()) {
-              // D8 relies on dead code removal to get rid of the dead phi itself.
-              if (value.numberOfAllUsers() != 0) {
-                BasicBlock block = value.asPhi().getBlock();
-                blockToAnalyze.add(block);
-                // Create a new constant, because it can be an existing constant that flow directly
-                // into the phi.
-                ConstNumber newConst = ConstNumber.copyOf(code, evaluatedConst);
-                InstructionListIterator iterator = block.listIterator();
-                Instruction inst = iterator.nextUntil((i) -> !i.isMoveException());
-                newConst.setPosition(inst.getPosition());
-                if (!inst.isDebugPosition()) {
-                  iterator.previous();
+        .filter(entry -> entry.getValue().isConst())
+        .forEach(
+            entry -> {
+              Value value = entry.getKey();
+              ConstNumber evaluatedConst = entry.getValue().asConst().getConstNumber();
+              if (value.definition != evaluatedConst) {
+                if (value.isPhi()) {
+                  // D8 relies on dead code removal to get rid of the dead phi itself.
+                  if (value.numberOfAllUsers() != 0) {
+                    BasicBlock block = value.asPhi().getBlock();
+                    blockToAnalyze.add(block);
+                    // Create a new constant, because it can be an existing constant that flow
+                    // directly
+                    // into the phi.
+                    ConstNumber newConst = ConstNumber.copyOf(code, evaluatedConst);
+                    InstructionListIterator iterator = block.listIterator(code);
+                    Instruction inst = iterator.nextUntil(i -> !i.isMoveException());
+                    newConst.setPosition(inst.getPosition());
+                    if (!inst.isDebugPosition()) {
+                      iterator.previous();
+                    }
+                    iterator.add(newConst);
+                    value.replaceUsers(newConst.outValue());
+                  }
+                } else {
+                  BasicBlock block = value.definition.getBlock();
+                  InstructionListIterator iterator = block.listIterator(code);
+                  iterator.nextUntil(i -> i == value.definition);
+                  iterator.replaceCurrentInstruction(evaluatedConst);
                 }
-                iterator.add(newConst);
-                value.replaceUsers(newConst.outValue());
               }
-            } else {
-              BasicBlock block = value.definition.getBlock();
-              InstructionListIterator iterator = block.listIterator();
-              Instruction toReplace = iterator.nextUntil((i) -> i == value.definition);
-              iterator.replaceCurrentInstruction(evaluatedConst);
-            }
-          }
-        });
+            });
 
     for (BasicBlock block : blockToAnalyze) {
       block.deduplicatePhis();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 118ea7a..e447e7a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -121,7 +121,7 @@
     infoValue.definition.replace(
         new ConstString(
             code.createValue(stringType), encoder.encodeInfo(protoMessageInfo), throwingInfo),
-        code.metadata());
+        code);
   }
 
   private void rewriteObjectsArgumentToNewMessageInfo(
@@ -131,8 +131,7 @@
       ProtoMessageInfo protoMessageInfo) {
     // Position iterator immediately before the call to newMessageInfo().
     BasicBlock block = newMessageInfoInvoke.getBlock();
-    InstructionListIterator instructionIterator =
-        block.listIterator(newMessageInfoInvoke).recordChangesToMetadata(code);
+    InstructionListIterator instructionIterator = block.listIterator(code, newMessageInfoInvoke);
     Instruction previous = instructionIterator.previous();
     instructionIterator.setInsertionPosition(newMessageInfoInvoke.getPosition());
     assert previous == newMessageInfoInvoke;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index 90f6e25..ddc98e2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -283,7 +283,7 @@
     }
 
     // Create an iterator for the block of interest.
-    InstructionListIterator instructionIterator = newArrayEmpty.getBlock().listIterator();
+    InstructionIterator instructionIterator = newArrayEmpty.getBlock().iterator();
     instructionIterator.nextUntil(instruction -> instruction == newArrayEmpty);
 
     return new ThrowingIterator<Value, InvalidRawMessageInfoException>() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index b018ce7..cf29fdc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -93,8 +93,8 @@
     return localsAtEntry;
   }
 
-  public void replaceLastInstruction(Instruction instruction) {
-    InstructionListIterator iterator = listIterator(getInstructions().size());
+  public void replaceLastInstruction(Instruction instruction, IRCode code) {
+    InstructionListIterator iterator = listIterator(code, getInstructions().size());
     iterator.previous();
     iterator.replaceCurrentInstruction(instruction);
   }
@@ -562,6 +562,10 @@
     return instructions;
   }
 
+  public Iterable<Instruction> instructionsAfter(Instruction instruction) {
+    return () -> iterator(instruction);
+  }
+
   public boolean isEmpty() {
     return instructions.isEmpty();
   }
@@ -578,7 +582,7 @@
 
   public Instruction exceptionalExit() {
     assert hasCatchHandlers();
-    ListIterator<Instruction> it = listIterator(instructions.size());
+    InstructionIterator it = iterator(instructions.size());
     while (it.hasPrevious()) {
       Instruction instruction = it.previous();
       if (instruction.instructionTypeCanThrow()) {
@@ -1321,7 +1325,7 @@
    *
    * @param blockNumber the block number of the block
    * @param theIf the if instruction
-   * @param instruction the instruction to place before the if instruction
+   * @param instructions the instructions to place before the if instruction
    */
   public static BasicBlock createIfBlock(int blockNumber, If theIf, Instruction... instructions) {
     BasicBlock block = new BasicBlock();
@@ -1494,7 +1498,7 @@
   // visible to exceptional successors.
   private boolean verifyNoValuesAfterThrowingInstruction() {
     if (hasCatchHandlers()) {
-      ListIterator<Instruction> iterator = listIterator(instructions.size());
+      InstructionIterator iterator = iterator(instructions.size());
       while (iterator.hasPrevious()) {
         Instruction instruction = iterator.previous();
         if (instruction.instructionTypeCanThrow()) {
@@ -1507,26 +1511,38 @@
   }
 
   public InstructionIterator iterator() {
-    return new BasicBlockInstructionListIterator(this);
+    return new BasicBlockInstructionIterator(this);
   }
 
-  public InstructionListIterator listIterator() {
-    return new BasicBlockInstructionListIterator(this);
+  public InstructionIterator iterator(int index) {
+    return new BasicBlockInstructionIterator(this, index);
   }
 
-  public InstructionListIterator listIterator(int index) {
-    return new BasicBlockInstructionListIterator(this, index);
+  public InstructionIterator iterator(Instruction instruction) {
+    return new BasicBlockInstructionIterator(this, instruction);
+  }
+
+  public InstructionListIterator listIterator(IRCode code) {
+    return listIterator(code.metadata());
+  }
+
+  public InstructionListIterator listIterator(IRMetadata metadata) {
+    return new BasicBlockInstructionListIterator(metadata, this);
+  }
+
+  public InstructionListIterator listIterator(IRCode code, int index) {
+    return new BasicBlockInstructionListIterator(code.metadata(), this, index);
   }
 
   /**
    * Creates an instruction list iterator starting at <code>instruction</code>.
    *
-   * The cursor will be positioned after <code>instruction</code>. Calling <code>next</code> on
+   * <p>The cursor will be positioned after <code>instruction</code>. Calling <code>next</code> on
    * the returned iterator will return the instruction after <code>instruction</code>. Calling
    * <code>previous</code> will return <code>instruction</code>.
    */
-  public InstructionListIterator listIterator(Instruction instruction) {
-    return new BasicBlockInstructionListIterator(this, instruction);
+  public InstructionListIterator listIterator(IRCode code, Instruction instruction) {
+    return new BasicBlockInstructionListIterator(code.metadata(), this, instruction);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
new file mode 100644
index 0000000..f0771c3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2019, 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.code;
+
+import java.util.ListIterator;
+
+public class BasicBlockInstructionIterator implements InstructionIterator {
+
+  private final ListIterator<Instruction> instructionIterator;
+
+  BasicBlockInstructionIterator(BasicBlock block) {
+    this.instructionIterator = block.getInstructions().listIterator();
+  }
+
+  BasicBlockInstructionIterator(BasicBlock block, int index) {
+    this.instructionIterator = block.getInstructions().listIterator(index);
+  }
+
+  BasicBlockInstructionIterator(BasicBlock block, Instruction instruction) {
+    this(block);
+    nextUntil(x -> x == instruction);
+  }
+
+  @Override
+  public boolean hasPrevious() {
+    return instructionIterator.hasPrevious();
+  }
+
+  @Override
+  public Instruction previous() {
+    return instructionIterator.previous();
+  }
+
+  @Override
+  public boolean hasNext() {
+    return instructionIterator.hasNext();
+  }
+
+  @Override
+  public Instruction next() {
+    return instructionIterator.next();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index f0ed3fc..90c1569 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -21,7 +21,6 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
@@ -33,27 +32,24 @@
   protected Instruction current;
   protected Position position = null;
 
-  private IRMetadata metadata;
+  private final IRMetadata metadata;
 
-  BasicBlockInstructionListIterator(BasicBlock block) {
+  BasicBlockInstructionListIterator(IRMetadata metadata, BasicBlock block) {
     this.block = block;
     this.listIterator = block.getInstructions().listIterator();
+    this.metadata = metadata;
   }
 
-  BasicBlockInstructionListIterator(BasicBlock block, int index) {
+  BasicBlockInstructionListIterator(IRMetadata metadata, BasicBlock block, int index) {
     this.block = block;
     this.listIterator = block.getInstructions().listIterator(index);
-  }
-
-  BasicBlockInstructionListIterator(BasicBlock block, Instruction instruction) {
-    this(block);
-    nextUntil((x) -> x == instruction);
-  }
-
-  @Override
-  public BasicBlockInstructionListIterator recordChangesToMetadata(IRMetadata metadata) {
     this.metadata = metadata;
-    return this;
+  }
+
+  BasicBlockInstructionListIterator(
+      IRMetadata metadata, BasicBlock block, Instruction instruction) {
+    this(metadata, block);
+    nextUntil(x -> x == instruction);
   }
 
   @Override
@@ -94,10 +90,10 @@
   }
 
   /**
-   * Adds an instruction to the block. The instruction will be added just before the current
-   * cursor position.
+   * Adds an instruction to the block. The instruction will be added just before the current cursor
+   * position.
    *
-   * The instruction will be assigned to the block it is added to.
+   * <p>The instruction will be assigned to the block it is added to.
    *
    * @param instruction The instruction to add.
    */
@@ -109,16 +105,14 @@
       instruction.setPosition(position);
     }
     listIterator.add(instruction);
-    if (metadata != null) {
-      metadata.record(instruction);
-    }
+    metadata.record(instruction);
   }
 
   /**
-   * Replaces the last instruction returned by {@link #next} or {@link #previous} with the
-   * specified instruction.
+   * Replaces the last instruction returned by {@link #next} or {@link #previous} with the specified
+   * instruction.
    *
-   * The instruction will be assigned to the block it is added to.
+   * <p>The instruction will be assigned to the block it is added to.
    *
    * @param instruction The instruction to replace with.
    */
@@ -133,10 +127,10 @@
    * Remove the current instruction (aka the {@link Instruction} returned by the previous call to
    * {@link #next}.
    *
-   * The current instruction will be completely detached from the instruction stream with uses
-   * of its in-values removed.
+   * <p>The current instruction will be completely detached from the instruction stream with uses of
+   * its in-values removed.
    *
-   * If the current instruction produces an out-value this out value must not have any users.
+   * <p>If the current instruction produces an out-value this out value must not have any users.
    */
   @Override
   public void remove() {
@@ -164,6 +158,15 @@
   }
 
   @Override
+  public void removeInstructionIgnoreOutValue() {
+    if (current == null) {
+      throw new IllegalStateException();
+    }
+    listIterator.remove();
+    current = null;
+  }
+
+  @Override
   public void removeOrReplaceByDebugLocalRead() {
     if (current == null) {
       throw new IllegalStateException();
@@ -193,9 +196,7 @@
     listIterator.remove();
     listIterator.add(newInstruction);
     current.clearBlock();
-    if (metadata != null) {
-      metadata.record(newInstruction);
-    }
+    metadata.record(newInstruction);
   }
 
   @Override
@@ -327,7 +328,7 @@
     BasicBlock newBlock = split(code, blocksIterator);
     assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == newBlock;
     // Skip the requested number of instructions and split again.
-    InstructionListIterator iterator = newBlock.listIterator();
+    InstructionListIterator iterator = newBlock.listIterator(code);
     for (int i = 0; i < instructions; i++) {
       iterator.next();
     }
@@ -337,7 +338,7 @@
   }
 
   private boolean canThrow(IRCode code) {
-    Iterator<Instruction> iterator = code.instructionIterator();
+    InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       boolean throwing = iterator.next().instructionTypeCanThrow();
       if (throwing) {
@@ -357,7 +358,7 @@
     // one throwing instruction in each block.
     // NOTE: This iterator is replaced in the loop below, so that the iteration continues in
     // the new block after the iterated block is split.
-    InstructionListIterator instructionsIterator = inlinedBlock.listIterator();
+    InstructionListIterator instructionsIterator = inlinedBlock.listIterator(code);
     BasicBlock currentBlock = inlinedBlock;
     while (currentBlock != null && instructionsIterator.hasNext()) {
       assert !currentBlock.hasCatchHandlers();
@@ -383,7 +384,7 @@
           BasicBlock b = blocksIterator.next();
           assert b == nextBlock;
           // Switch iteration to the split block.
-          instructionsIterator = nextBlock.listIterator();
+          instructionsIterator = nextBlock.listIterator(code);
         } else {
           instructionsIterator = null;
         }
@@ -497,15 +498,15 @@
         // the inlinee (by the call to appendCatchHandlers() later in this method), so we don't
         // need to do anything about that here.
         BasicBlock inlineEntry = entryBlock;
-        entryBlock = entryBlock.listIterator().split(inlinee);
-        entryBlockIterator = entryBlock.listIterator();
+        entryBlock = entryBlock.listIterator(code).split(inlinee);
+        entryBlockIterator = entryBlock.listIterator(code);
         // Insert cast instruction into the new block.
         inlineEntry.getInstructions().addFirst(castInstruction);
         castInstruction.setBlock(inlineEntry);
         assert castInstruction.getBlock().getInstructions().size() == 2;
       } else {
         castInstruction.setBlock(entryBlock);
-        entryBlockIterator = entryBlock.listIterator();
+        entryBlockIterator = entryBlock.listIterator(code);
         entryBlockIterator.add(castInstruction);
       }
 
@@ -516,7 +517,7 @@
       removeArgumentInstruction(entryBlockIterator, argument);
       i++;
     } else {
-      entryBlockIterator = entryBlock.listIterator();
+      entryBlockIterator = entryBlock.listIterator(code);
     }
 
     // Map the remaining argument values.
@@ -560,16 +561,16 @@
       // Split before return and unlink return.
       BasicBlock returnBlock = inlineeIterator.split(inlinee);
       inlineExit = returnBlock.unlinkSinglePredecessor();
-      InstructionListIterator returnBlockIterator = returnBlock.listIterator();
+      InstructionListIterator returnBlockIterator = returnBlock.listIterator(code);
       returnBlockIterator.next();
-      returnBlockIterator.remove();  // This clears out the users from the return.
+      returnBlockIterator.remove(); // This clears out the users from the return.
       assert !returnBlockIterator.hasNext();
       inlinee.blocks.remove(returnBlock);
 
       // Leaving the invoke block in the graph as an empty block. Still unlink its predecessor as
       // the exit block of the inlinee will become its new predecessor.
       invokeBlock.unlinkSinglePredecessor();
-      InstructionListIterator invokeBlockIterator = invokeBlock.listIterator();
+      InstructionListIterator invokeBlockIterator = invokeBlock.listIterator(code);
       invokeBlockIterator.next();
       invokeBlockIterator.remove();
       invokeSuccessor = invokeBlock;
@@ -633,7 +634,7 @@
   private InstructionListIterator ensureSingleReturnInstruction(
       AppView<?> appView, IRCode code, List<BasicBlock> normalExits) {
     if (normalExits.size() == 1) {
-      InstructionListIterator it = normalExits.get(0).listIterator();
+      InstructionListIterator it = normalExits.get(0).listIterator(code);
       it.nextUntil(Instruction::isReturn);
       it.previous();
       return it;
@@ -673,7 +674,7 @@
     newReturn.setPosition(Position.none());
     newExitBlock.add(newReturn);
     for (BasicBlock exitBlock : normalExits) {
-      InstructionListIterator it = exitBlock.listIterator(exitBlock.getInstructions().size());
+      InstructionListIterator it = exitBlock.listIterator(code, exitBlock.getInstructions().size());
       Instruction oldExit = it.previous();
       assert oldExit.isReturn();
       it.replaceCurrentInstruction(new Goto());
@@ -682,6 +683,6 @@
     newExitBlock.close(null);
     code.blocks.add(newExitBlock);
     assert code.isConsistentSSA();
-    return newExitBlock.listIterator();
+    return newExitBlock.listIterator(code);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
index 26305b7..d9fc837 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.code;
 
-import java.util.Iterator;
 import java.util.ListIterator;
 
 public class BasicBlockIterator implements ListIterator<BasicBlock> {
@@ -79,7 +78,7 @@
       throw new IllegalStateException();
     }
     // Remove all instructions from the block before removing the block.
-    Iterator<Instruction> iterator = current.iterator();
+    InstructionListIterator iterator = current.listIterator(code);
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       instruction.clearDebugValues();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index bc53af5..9198796 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -31,7 +31,6 @@
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -198,9 +197,9 @@
       // normal successor.
       assert liveStack.isEmpty()
           || block.getSuccessors().size() - exceptionalSuccessors.size() == 1;
-      Iterator<Instruction> iterator = block.getInstructions().descendingIterator();
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
+      InstructionIterator iterator = block.listIterator(this, block.getInstructions().size());
+      while (iterator.hasPrevious()) {
+        Instruction instruction = iterator.previous();
         Value outValue = instruction.outValue();
         if (outValue != null) {
           if (outValue instanceof StackValue) {
@@ -703,7 +702,7 @@
   private boolean consistentBlockInstructions() {
     boolean argumentsAllowed = true;
     for (BasicBlock block : blocks) {
-      block.consistentBlockInstructions(
+      assert block.consistentBlockInstructions(
           argumentsAllowed,
           options.debug || method.getOptimizationInfo().isReachabilitySensitive());
       argumentsAllowed = false;
@@ -823,6 +822,10 @@
   }
 
   public InstructionIterator instructionIterator() {
+    return new IRCodeInstructionIterator(this);
+  }
+
+  public InstructionListIterator instructionListIterator() {
     return new IRCodeInstructionListIterator(this);
   }
 
@@ -853,11 +856,9 @@
   }
 
   public int numberRemainingInstructions() {
-    InstructionIterator it = instructionIterator();
-    while (it.hasNext()) {
-      Instruction i = it.next();
-      if (i.getNumber() == -1) {
-        i.setNumber(nextInstructionNumber);
+    for (Instruction instruction : instructions()) {
+      if (instruction.getNumber() == -1) {
+        instruction.setNumber(nextInstructionNumber);
         nextInstructionNumber += INSTRUCTION_NUMBER_DELTA;
       }
     }
@@ -874,7 +875,7 @@
 
   public List<Value> collectArguments(boolean ignoreReceiver) {
     final List<Value> arguments = new ArrayList<>();
-    Iterator<Instruction> iterator = entryBlock().iterator();
+    InstructionIterator iterator = entryBlock().iterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isArgument()) {
@@ -893,7 +894,7 @@
     if (method.accessFlags.isStatic()) {
       return null;
     }
-    Instruction firstArg = entryBlock().listIterator().nextUntil(Instruction::isArgument);
+    Instruction firstArg = entryBlock().iterator().nextUntil(Instruction::isArgument);
     assert firstArg != null;
     Value thisValue = firstArg.asArgument().outValue();
     assert thisValue.isThis();
@@ -942,9 +943,7 @@
   }
 
   private boolean computeAllThrowingInstructionsHavePositions() {
-    InstructionIterator it = instructionIterator();
-    while (it.hasNext()) {
-      Instruction instruction = it.next();
+    for (Instruction instruction : instructions()) {
       if (instruction.instructionTypeCanThrow()
           && !instruction.isConstString()
           && !instruction.isDexItemBasedConstString()
@@ -1029,7 +1028,7 @@
       }
       return result;
     } else {
-      Iterable<Instruction> result = () -> source.listIterator(instruction);
+      Iterable<Instruction> result = () -> source.iterator(instruction);
       for (BasicBlock block : blocksReachableFromSource) {
         result = Iterables.concat(result, block.getInstructions());
       }
@@ -1129,7 +1128,7 @@
 
   public Position findFirstNonNonePosition() {
     Instruction rightAfterArguments =
-        entryBlock().listIterator().nextUntil(instr -> !instr.isArgument());
+        entryBlock().iterator().nextUntil(instr -> !instr.isArgument());
     Position firstNonArgumentPosition = rightAfterArguments.getPosition();
     Set<BasicBlock> visitedBlocks = new HashSet<>();
     while (rightAfterArguments != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionIterator.java
new file mode 100644
index 0000000..4f131a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionIterator.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2019, 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.code;
+
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+public class IRCodeInstructionIterator implements InstructionIterator {
+
+  private final ListIterator<BasicBlock> blockIterator;
+  private InstructionListIterator instructionIterator;
+
+  private final IRCode code;
+
+  public IRCodeInstructionIterator(IRCode code) {
+    this.blockIterator = code.listIterator();
+    this.code = code;
+    this.instructionIterator = blockIterator.next().listIterator(code);
+  }
+
+  @Override
+  public boolean hasNext() {
+    return instructionIterator.hasNext() || blockIterator.hasNext();
+  }
+
+  @Override
+  public Instruction next() {
+    if (instructionIterator.hasNext()) {
+      return instructionIterator.next();
+    }
+    if (!blockIterator.hasNext()) {
+      throw new NoSuchElementException();
+    }
+    instructionIterator = blockIterator.next().listIterator(code);
+    assert instructionIterator.hasNext();
+    return instructionIterator.next();
+  }
+
+  @Override
+  public boolean hasPrevious() {
+    return instructionIterator.hasPrevious() || blockIterator.hasPrevious();
+  }
+
+  @Override
+  public Instruction previous() {
+    if (instructionIterator.hasPrevious()) {
+      return instructionIterator.previous();
+    }
+    if (!blockIterator.hasPrevious()) {
+      throw new NoSuchElementException();
+    }
+    BasicBlock block = blockIterator.previous();
+    instructionIterator = block.listIterator(code, block.getInstructions().size());
+    assert instructionIterator.hasPrevious();
+    return instructionIterator.previous();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index c7f8414..60e06c6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -18,11 +18,12 @@
   private final ListIterator<BasicBlock> blockIterator;
   private InstructionListIterator instructionIterator;
 
-  private IRMetadata metadata;
+  private final IRCode code;
 
   public IRCodeInstructionListIterator(IRCode code) {
-    blockIterator = code.listIterator();
-    instructionIterator = blockIterator.next().listIterator();
+    this.blockIterator = code.listIterator();
+    this.code = code;
+    this.instructionIterator = blockIterator.next().listIterator(code);
   }
 
   @Override
@@ -67,12 +68,6 @@
   }
 
   @Override
-  public IRCodeInstructionListIterator recordChangesToMetadata(IRMetadata metadata) {
-    this.metadata = metadata;
-    return this;
-  }
-
-  @Override
   public boolean hasNext() {
     return instructionIterator.hasNext() || blockIterator.hasNext();
   }
@@ -85,7 +80,7 @@
     if (!blockIterator.hasNext()) {
       throw new NoSuchElementException();
     }
-    instructionIterator = blockIterator.next().listIterator();
+    instructionIterator = blockIterator.next().listIterator(code);
     assert instructionIterator.hasNext();
     return instructionIterator.next();
   }
@@ -104,7 +99,7 @@
       throw new NoSuchElementException();
     }
     BasicBlock block = blockIterator.previous();
-    instructionIterator = block.listIterator(block.getInstructions().size());
+    instructionIterator = block.listIterator(code, block.getInstructions().size());
     assert instructionIterator.hasPrevious();
     return instructionIterator.previous();
   }
@@ -122,9 +117,6 @@
   @Override
   public void add(Instruction instruction) {
     instructionIterator.add(instruction);
-    if (metadata != null) {
-      metadata.record(instruction);
-    }
   }
 
   @Override
@@ -135,17 +127,11 @@
   @Override
   public void set(Instruction instruction) {
     instructionIterator.set(instruction);
-    if (metadata != null) {
-      metadata.record(instruction);
-    }
   }
 
   @Override
   public void replaceCurrentInstruction(Instruction newInstruction) {
     instructionIterator.replaceCurrentInstruction(newInstruction);
-    if (metadata != null) {
-      metadata.record(newInstruction);
-    }
   }
 
   @Override
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 e9de61e..33309d2 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
@@ -270,15 +270,12 @@
     block = null;
   }
 
-  public void removeOrReplaceByDebugLocalRead() {
-    getBlock().listIterator(this).removeOrReplaceByDebugLocalRead();
+  public void removeOrReplaceByDebugLocalRead(IRCode code) {
+    getBlock().listIterator(code, this).removeOrReplaceByDebugLocalRead();
   }
 
-  public void replace(Instruction newInstruction, IRMetadata metadata) {
-    getBlock()
-        .listIterator(this)
-        .recordChangesToMetadata(metadata)
-        .replaceCurrentInstruction(newInstruction);
+  public void replace(Instruction newInstruction, IRCode code) {
+    getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
index d957147..539f0d7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
@@ -4,42 +4,48 @@
 
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.errors.Unreachable;
-import java.util.ListIterator;
+import java.util.Iterator;
 
-public interface InstructionIterator
-    extends ListIterator<Instruction>, NextUntilIterator<Instruction> {
+public interface InstructionIterator extends Iterator<Instruction>, NextUntilIterator<Instruction> {
 
-  default InstructionIterator recordChangesToMetadata(IRCode code) {
-    return recordChangesToMetadata(code.metadata());
+  /** @deprecated Use {@link InstructionListIterator#remove()} instead. */
+  @Deprecated
+  @Override
+  default void remove() {
+    throw new UnsupportedOperationException("remove");
   }
 
-  default InstructionIterator recordChangesToMetadata(IRMetadata metadata) {
-    throw new Unreachable(
-        "Method recordChangesToMetadata(IRMetadata) not implemented for "
-            + getClass().getTypeName());
-  }
+  boolean hasPrevious();
+
+  Instruction previous();
+
   /**
-   * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
-   * {@link #next} with the passed in <code>newInstruction</code>.
-   * <p>
-   * The current instruction will be completely detached from the instruction stream with uses
-   * of its in-values removed.
-   * <p>
-   * If the current instruction produces an out-value the new instruction must also produce
-   * an out-value, and all uses of the current instructions out-value will be replaced by the
-   * new instructions out-value.
-   * <p>
-   * The debug information of the current instruction will be attached to the new instruction.
+   * Peek the next instruction.
    *
-   * @param newInstruction the instruction to insert instead of the current.
+   * @return what will be returned by calling {@link #next}. If there is no next instruction <code>
+   *     null</code> is returned.
    */
-  void replaceCurrentInstruction(Instruction newInstruction);
-
+  default Instruction peekNext() {
+    Instruction next = null;
+    if (hasNext()) {
+      next = next();
+      previous();
+    }
+    return next;
+  }
 
   /**
-   * Safe removal function that will insert a DebugLocalRead to take over the debug values if any
-   * are associated with the current instruction.
+   * Peek the previous instruction.
+   *
+   * @return what will be returned by calling {@link #previous}. If there is no previous instruction
+   *     <code>null</code> is returned.
    */
-  void removeOrReplaceByDebugLocalRead();
+  default Instruction peekPrevious() {
+    Instruction previous = null;
+    if (hasPrevious()) {
+      previous = previous();
+      next();
+    }
+    return previous;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 773bc13..f4ff24a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -14,49 +14,40 @@
 import java.util.Set;
 
 public interface InstructionListIterator
-    extends InstructionIterator, PreviousUntilIterator<Instruction> {
+    extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
 
   /**
-   * Peek the previous instruction.
+   * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
+   * {@link #next} with the passed in <code>newInstruction</code>.
    *
-   * @return what will be returned by calling {@link #previous}. If there is no previous instruction
-   * <code>null</code> is returned.
+   * <p>The current instruction will be completely detached from the instruction stream with uses of
+   * its in-values removed.
+   *
+   * <p>If the current instruction produces an out-value the new instruction must also produce an
+   * out-value, and all uses of the current instructions out-value will be replaced by the new
+   * instructions out-value.
+   *
+   * <p>The debug information of the current instruction will be attached to the new instruction.
+   *
+   * @param newInstruction the instruction to insert instead of the current.
    */
-  default Instruction peekPrevious() {
-    Instruction previous = null;
-    if (hasPrevious()) {
-      previous = previous();
-      next();
-    }
-    return previous;
+  void replaceCurrentInstruction(Instruction newInstruction);
+
+  // Do not show a deprecation warning for InstructionListIterator.remove().
+  @SuppressWarnings("deprecation")
+  @Override
+  void remove();
+
+  // Removes the current instruction, even if it has an out-value that is used.
+  default void removeInstructionIgnoreOutValue() {
+    throw new Unimplemented();
   }
 
   /**
-   * Peek the next instruction.
-   *
-   * @return what will be returned by calling {@link #next}. If there is no next instruction
-   * <code>null</code> is returned.
+   * Safe removal function that will insert a DebugLocalRead to take over the debug values if any
+   * are associated with the current instruction.
    */
-  default Instruction peekNext() {
-    Instruction next = null;
-    if (hasNext()) {
-      next = next();
-      previous();
-    }
-    return next;
-  }
-
-  @Override
-  default InstructionListIterator recordChangesToMetadata(IRCode code) {
-    return recordChangesToMetadata(code.metadata());
-  }
-
-  @Override
-  default InstructionListIterator recordChangesToMetadata(IRMetadata metadata) {
-    throw new Unreachable(
-        "Method recordChangesToMetadata(IRMetadata) not implemented for "
-            + getClass().getTypeName());
-  }
+  void removeOrReplaceByDebugLocalRead();
 
   default void setInsertionPosition(Position position) {
     // Intentionally empty.
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionIterator.java
rename to src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index c14da47..2c76690 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -11,18 +11,21 @@
 import java.util.ListIterator;
 import java.util.Set;
 
-public class LinearFlowInstructionIterator implements InstructionIterator, InstructionListIterator {
+public class LinearFlowInstructionListIterator implements InstructionListIterator {
+
+  private final IRCode code;
 
   private BasicBlock currentBlock;
   private InstructionListIterator currentBlockIterator;
 
-  public LinearFlowInstructionIterator(BasicBlock block) {
-    this(block, 0);
+  public LinearFlowInstructionListIterator(IRCode code, BasicBlock block) {
+    this(code, block, 0);
   }
 
-  public LinearFlowInstructionIterator(BasicBlock block, int index) {
+  public LinearFlowInstructionListIterator(IRCode code, BasicBlock block, int index) {
+    this.code = code;
     this.currentBlock = block;
-    this.currentBlockIterator = block.listIterator(index);
+    this.currentBlockIterator = block.listIterator(code, index);
     // If index is pointing after the last instruction, and it is a goto with a linear edge,
     // we have to move the pointer. This is achieved by calling previous and next.
     if (index > 0) {
@@ -119,7 +122,7 @@
       target = candidate;
     }
     currentBlock = target;
-    currentBlockIterator = currentBlock.listIterator();
+    currentBlockIterator = currentBlock.listIterator(code);
     return currentBlockIterator.next();
   }
 
@@ -156,7 +159,7 @@
       return currentBlockIterator.previous();
     }
     currentBlock = target;
-    currentBlockIterator = currentBlock.listIterator(currentBlock.getInstructions().size());
+    currentBlockIterator = currentBlock.listIterator(code, currentBlock.getInstructions().size());
     // Iterate over the jump.
     currentBlockIterator.previous();
     return currentBlockIterator.previous();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 591df9e..3208833 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -856,9 +856,7 @@
     // creation.
     Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
 
-    InstructionListIterator instructionIterator = definition.getBlock().listIterator(definition);
-    while (instructionIterator.hasNext()) {
-      Instruction instruction = instructionIterator.next();
+    for (Instruction instruction : definition.getBlock().instructionsAfter(definition)) {
       if (instruction.isArrayPut()) {
         ArrayPut arrayPut = instruction.asArrayPut();
         Value array = arrayPut.array().getAliasedValue();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 7d6bfb0..db2d7bb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -36,7 +36,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Inc;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.JumpInstruction;
@@ -221,7 +220,7 @@
 
   private void rewriteNots() {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction current = it.next();
         if (!current.isNot()) {
@@ -357,7 +356,7 @@
 
   private void rewriteIincPatterns() {
     for (BasicBlock block : code.blocks) {
-      ListIterator<Instruction> it = block.getInstructions().listIterator();
+      InstructionListIterator it = block.listIterator(code);
       // Test that we have enough instructions for iinc.
       while (IINC_PATTERN_SIZE <= block.getInstructions().size() - it.nextIndex()) {
         Instruction loadOrConst1 = it.next();
@@ -403,11 +402,11 @@
             || position != store.getPosition()) {
           continue;
         }
-        it.remove();
+        it.removeInstructionIgnoreOutValue();
         it.next();
-        it.remove();
+        it.removeInstructionIgnoreOutValue();
         it.next();
-        it.remove();
+        it.removeInstructionIgnoreOutValue();
         it.next();
         Inc inc = new Inc(store.outValue(), load.inValues().get(0), increment);
         inc.setPosition(position);
@@ -430,9 +429,7 @@
         pendingFrame = null;
       }
     }
-    InstructionIterator it = block.iterator();
-    while (it.hasNext()) {
-      Instruction instruction = it.next();
+    for (Instruction instruction : block.getInstructions()) {
       if (fallthrough && instruction.isGoto()) {
         assert block.exit() == instruction;
         return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index ba9a714..75c2a15 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Move;
@@ -203,9 +204,8 @@
 
       // Compute offsets.
       offset = 0;
-      InstructionIterator it = ir.instructionIterator();
-      while (it.hasNext()) {
-        Info info = getInfo(it.next());
+      for (com.android.tools.r8.ir.code.Instruction instruction : ir.instructions()) {
+        Info info = getInfo(instruction);
         info.setOffset(offset);
         offset += info.computeSize(this);
         ++numberOfInstructions;
@@ -216,19 +216,17 @@
     DexDebugEventBuilder debugEventBuilder = new DexDebugEventBuilder(ir, options);
     List<Instruction> dexInstructions = new ArrayList<>(numberOfInstructions);
     int instructionOffset = 0;
-    InstructionIterator instructionIterator = ir.instructionIterator();
-    while (instructionIterator.hasNext()) {
-      com.android.tools.r8.ir.code.Instruction ir = instructionIterator.next();
-      Info info = getInfo(ir);
+    for (com.android.tools.r8.ir.code.Instruction irInstruction : ir.instructions()) {
+      Info info = getInfo(irInstruction);
       int previousInstructionCount = dexInstructions.size();
       info.addInstructions(this, dexInstructions);
       int instructionStartOffset = instructionOffset;
       while (previousInstructionCount < dexInstructions.size()) {
-        Instruction instruction = dexInstructions.get(previousInstructionCount++);
-        instruction.setOffset(instructionOffset);
-        instructionOffset += instruction.getSize();
+        Instruction dexInstruction = dexInstructions.get(previousInstructionCount++);
+        dexInstruction.setOffset(instructionOffset);
+        instructionOffset += dexInstruction.getSize();
       }
-      debugEventBuilder.add(instructionStartOffset, instructionOffset, ir);
+      debugEventBuilder.add(instructionStartOffset, instructionOffset, irInstruction);
     }
 
     // Workaround dalvik tracing bug, where the dalvik tracing JIT can end up tracing
@@ -397,9 +395,7 @@
       BasicBlock nextBlock =
           blockIndex + 1 < code.blocks.size() ? code.blocks.get(blockIndex + 1) : null;
 
-      InstructionIterator iterator = currentBlock.iterator();
-      while (iterator.hasNext()) {
-        com.android.tools.r8.ir.code.Instruction instruction = iterator.next();
+      for (com.android.tools.r8.ir.code.Instruction instruction : currentBlock.getInstructions()) {
         if (instruction.isDebugPosition()) {
           if (unresolvedPosition == null
               && currentMaterializedPosition == instruction.getPosition()) {
@@ -444,7 +440,7 @@
     }
     // Remove all unneeded positions.
     if (!toRemove.isEmpty()) {
-      InstructionIterator it = code.instructionIterator();
+      InstructionListIterator it = code.instructionListIterator();
       int i = 0;
       while (it.hasNext() && i < toRemove.size()) {
         if (it.next() == toRemove.get(i)) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index cf72ee6..04bcfa8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -414,9 +414,10 @@
   // Flag indicating if any values have imprecise types.
   private boolean hasImpreciseValues = false;
 
-  // Flag indicating if a const string is ever loaded.
-
-  // Flag indicating if the code has a monitor instruction.
+  // Information about which kinds of instructions that may be present in the IR. This information
+  // is sound (i.e., if the IR has a const-string instruction then metadata.mayHaveConstString()
+  // returns true) but not necessarily complete (i.e., if metadata.mayHaveConstString() returns true
+  // then the IR does not necessarily contain a const-string instruction).
   private final IRMetadata metadata = new IRMetadata();
 
   public IRBuilder(DexEncodedMethod method, AppView<?> appView, SourceCode source, Origin origin) {
@@ -558,7 +559,7 @@
     // Insert definitions for all uninitialized local values.
     if (uninitializedDebugLocalValues != null) {
       Position position = entryBlock.getPosition();
-      InstructionListIterator it = entryBlock.listIterator();
+      InstructionListIterator it = entryBlock.listIterator(metadata);
       it.nextUntil(i -> !i.isArgument());
       it.previous();
       for (List<Value> values : uninitializedDebugLocalValues.values()) {
@@ -633,7 +634,7 @@
       return;
     }
     for (BasicBlock block : blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(metadata);
       Position current = null;
       while (it.hasNext()) {
         Instruction instruction = it.next();
@@ -646,7 +647,6 @@
             it.removeOrReplaceByDebugLocalRead();
           } else {
             current = position;
-            metadata.record(instruction);
           }
         } else if (position.isSome() && !position.synthetic && !position.equals(current)) {
           DebugPosition positionChange = new DebugPosition();
@@ -655,7 +655,6 @@
           it.add(positionChange);
           it.next();
           current = position;
-          metadata.record(positionChange);
         }
       }
     }
@@ -823,7 +822,6 @@
   public void add(Instruction ir) {
     assert !ir.isJumpInstruction();
     addInstruction(ir);
-    metadata.record(ir);
   }
 
   private RemovedArgumentInfo getRemovedArgumentInfo() {
@@ -2210,6 +2208,7 @@
   // Private instruction helpers.
   private void addInstruction(Instruction ir) {
     addInstruction(ir, source.getCurrentPosition());
+    metadata.record(ir);
   }
 
   private void addInstruction(Instruction ir, Position position) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 3c66edf..eabba6f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1587,7 +1587,7 @@
     // Only if the constructor contains a super constructor call taking only parameters as
     // inputs.
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       Instruction superConstructorCall = it.nextUntil((i) ->
           i.isInvokeDirect() &&
           i.asInvokeDirect().getInvokedMethod().name == options.itemFactory.constructorMethodName &&
@@ -1630,7 +1630,7 @@
                     factory.intDescriptor,
                     new DexString[] {factory.longDescriptor}));
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       Instruction firstMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
       if (!isLongMul(firstMaterializing)) {
         continue;
@@ -1645,7 +1645,7 @@
       Value outOfMul = firstMaterializing.outValue();
       for (Value inOfAddOrSub : secondMaterializing.inValues()) {
         if (isAliasOf(inOfAddOrSub, outOfMul)) {
-          it = block.listIterator();
+          it = block.listIterator(code);
           it.nextUntil(i -> i == firstMaterializing);
           Value longValue = firstMaterializing.inValues().get(0);
           InvokeStatic invokeLongSignum =
@@ -1707,7 +1707,7 @@
       BasicBlock split = it.split(code);
       assert split.hasCatchHandlers();
       assert !block.hasCatchHandlers();
-      it = block.listIterator(block.getInstructions().size() - 1);
+      it = block.listIterator(code, block.getInstructions().size() - 1);
     }
     instruction.setPosition(addBefore.getPosition());
     it.add(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index ae94a4e..a02bf6c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -121,7 +121,7 @@
           mayHaveUnreachableBlocks |= unlinkDeadCatchHandlers(block);
         }
       }
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeCustom()) {
@@ -277,7 +277,7 @@
                 // Split the block to ensure no instructions after throwing instructions.
                 iterator
                     .split(code, blocks)
-                    .listIterator()
+                    .listIterator(code)
                     .add(constantReturnMaterializingInstruction);
               } else {
                 iterator.add(constantReturnMaterializingInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index f1396b8..964fcd1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -289,8 +289,7 @@
       insertionBlock.link(fallthroughBlock);
       JumpInstruction exit = insertionBlock.exit();
       exit.replace(
-          new StringSwitch(value, keys, targetBlockIndices, i + numberOfCatchHandlers),
-          code.metadata());
+          new StringSwitch(value, keys, targetBlockIndices, i + numberOfCatchHandlers), code);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index 4cf5299..d163c21 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -112,7 +112,7 @@
 
       if (previous == null) {
         // Replace the string-switch instruction by a goto instruction.
-        block.exit().replace(new Goto(newBlock), code.metadata());
+        block.exit().replace(new Goto(newBlock), code);
         block.link(newBlock);
       } else {
         // Set the fallthrough block for the previously added if-instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index b178863..d534cf6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -24,7 +24,7 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -94,7 +94,7 @@
       return; // Nothing to do!
     }
 
-    InstructionIterator iterator = code.instructionIterator();
+    InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (!instruction.isInvokeMethod()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index b159ef7..95a70df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -59,7 +59,7 @@
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator instructions = block.listIterator();
+      InstructionListIterator instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
         if (instruction.isInvokeMethod() && !instruction.isInvokeSuper()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 0681e81..a241bf9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -204,7 +204,7 @@
     AppInfo appInfo = appView.appInfo();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator instructions = block.listIterator();
+      InstructionListIterator instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 9dff900..fb64572 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -117,7 +117,7 @@
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator instructions = block.listIterator();
+      InstructionListIterator instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
         if (instruction.isInvokeCustom()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index 785799d..834a492 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -117,7 +117,7 @@
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator instructions = block.listIterator().recordChangesToMetadata(code);
+      InstructionListIterator instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
         if (!instruction.isInvokeCustom()) {
@@ -382,7 +382,7 @@
       // located right before the iterator point and new blocks created while copying
       // handles break this expectation.
       List<BasicBlock> newBlocks = new ArrayList<>();
-      InstructionListIterator it = currentBlock.listIterator();
+      InstructionListIterator it = currentBlock.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.instructionTypeCanThrow() && it.hasNext()) {
@@ -391,7 +391,7 @@
           BasicBlock newBlock = it.split(code, blocks);
           newBlocks.add(newBlock);
           // Follow with the next block.
-          it = newBlock.listIterator();
+          it = newBlock.listIterator(code);
         }
       }
       // Copy catch handlers after all blocks are split.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index efa0901..eb7cc6d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.synthetic.TemplateMethodCode;
@@ -76,7 +76,7 @@
 
   // Rewrites calls to $closeResource() method. Can be invoked concurrently.
   public void rewriteMethodCode(IRCode code) {
-    InstructionIterator iterator = code.instructionIterator();
+    InstructionListIterator iterator = code.instructionListIterator();
     AppInfo appInfo = appView.appInfo();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 0c1ffad..257d002 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -90,7 +90,7 @@
         if (blocksToBeRemoved.contains(block)) {
           continue;
         }
-        InstructionListIterator instructionIterator = block.listIterator();
+        InstructionListIterator instructionIterator = block.listIterator(code);
         while (instructionIterator.hasNext()) {
           Instruction instruction = instructionIterator.next();
           if (instruction.isAssumeDynamicType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 8589552..ae8ec3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -37,7 +37,6 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -168,7 +167,7 @@
 
     // Note: Traversing code.instructions(), and not unnecessaryStaticPuts(), to ensure
     // deterministic iteration order.
-    InstructionIterator instructionIterator = code.instructionIterator();
+    InstructionListIterator instructionIterator = code.instructionListIterator();
     while (instructionIterator.hasNext()) {
       Instruction instruction = instructionIterator.next();
       if (!instruction.isStaticPut()
@@ -196,7 +195,7 @@
 
     // Remove the instructions collected for removal.
     if (unnecessaryInstructions.size() > 0) {
-      IteratorUtils.removeIf(code.instructionIterator(), unnecessaryInstructions::contains);
+      IteratorUtils.removeIf(code.instructionListIterator(), unnecessaryInstructions::contains);
     }
 
     // If we are in R8, and we have removed all static-put instructions to some field, then record
@@ -325,9 +324,7 @@
       BasicBlock block = code.entryBlock();
       while (!block.isMarked(color) && block.getPredecessors().size() <= 1) {
         block.mark(color);
-        InstructionListIterator it = block.listIterator();
-        while (it.hasNext()) {
-          Instruction instruction = it.next();
+        for (Instruction instruction : block.getInstructions()) {
           if (instruction.isArrayPut()) {
             // Array stores do not impact our ability to move constants into the class definition,
             // as long as the instructions do not throw.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 24b3f6e..28557ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -182,7 +182,7 @@
     // is not nullable. This is fixed by rerunning the type analysis for the affected values.
     Set<Value> valuesThatRequireWidening = Sets.newIdentityHashSet();
 
-    InstructionIterator it = code.instructionIterator();
+    InstructionListIterator it = code.instructionListIterator();
     boolean needToCheckTrivialPhis = false;
     while (it.hasNext()) {
       Instruction instruction = it.next();
@@ -255,7 +255,7 @@
   // Rewrite 'throw new NullPointerException()' to 'throw null'.
   public void rewriteThrowNullPointerException(IRCode code) {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         // Check for 'new-instance NullPointerException' with 2 users, not declaring a local and
@@ -421,11 +421,9 @@
       // there is nothing to do.
       return;
     }
-    InstructionIterator it = code.instructionIterator();
     int selfRecursionFanOut = 0;
     Instruction lastSelfRecursiveCall = null;
-    while (it.hasNext()) {
-      Instruction i = it.next();
+    for (Instruction i : code.instructions()) {
       if (i.isInvokeMethod() && i.asInvokeMethod().getInvokedMethod() == code.method.method) {
         selfRecursionFanOut++;
         lastSelfRecursiveCall = i;
@@ -435,7 +433,7 @@
       assert lastSelfRecursiveCall != null;
       // Split out the last recursive call in its own block.
       InstructionListIterator splitIterator =
-          lastSelfRecursiveCall.getBlock().listIterator(lastSelfRecursiveCall);
+          lastSelfRecursiveCall.getBlock().listIterator(code, lastSelfRecursiveCall);
       splitIterator.previous();
       BasicBlock newBlock = splitIterator.split(code, 1);
       // Generate rethrow block.
@@ -863,7 +861,7 @@
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
     while (blocksIterator.hasNext()) {
       BasicBlock block = blocksIterator.next();
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
         if (instruction.isIntSwitch()) {
@@ -1090,7 +1088,7 @@
                   targets,
                   switchInsn.getFallthroughBlockIndex());
           // Replace the switch itself.
-          exit.replace(newSwitch, code.metadata());
+          exit.replace(newSwitch, code);
           // If the original input to the switch is now unused, remove it too. It is not dead
           // as it might have side-effects but we ignore these here.
           Instruction arrayGet = info.arrayGet;
@@ -1368,10 +1366,7 @@
       IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
     Value receiver = code.getThis();
 
-    InstructionIterator it = code.instructionIterator();
-    while (it.hasNext()) {
-      Instruction insn = it.next();
-
+    for (Instruction insn : code.instructions()) {
       if (insn.isReturn()) {
         continue;
       }
@@ -1455,14 +1450,10 @@
   //    and assign this instance to a static final field of the same class.
   //
   private synchronized TrivialInitializer computeClassInitializerInfo(IRCode code, DexClass clazz) {
-    InstructionIterator it = code.instructionIterator();
-
     Value createdSingletonInstance = null;
     DexField singletonField = null;
 
-    while (it.hasNext()) {
-      Instruction insn = it.next();
-
+    for (Instruction insn : code.instructions()) {
       if (insn.isConstNumber()) {
         continue;
       }
@@ -1578,9 +1569,7 @@
               // We found a NPE (or similar exception) throwing code.
               // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on the value.
               for (BasicBlock predecessor : currentBlock.getPredecessors()) {
-                Instruction last =
-                    predecessor.listIterator(predecessor.getInstructions().size()).previous();
-                if (isNullCheck(last, value)) {
+                if (isNullCheck(predecessor.exit(), value)) {
                   return InstructionEffect.DESIRED_EFFECT;
                 }
               }
@@ -1652,11 +1641,11 @@
   }
 
   /**
-   * Returns true if the given instruction is {@code v <- new-instance NullPointerException},
-   * and the next instruction is {@code invoke-direct v, NullPointerException.<init>()}.
+   * Returns true if the given instruction is {@code v <- new-instance NullPointerException}, and
+   * the next instruction is {@code invoke-direct v, NullPointerException.<init>()}.
    */
   private static boolean isInstantiationOfNullPointerException(
-      Instruction instruction, InstructionListIterator it, DexItemFactory dexItemFactory) {
+      Instruction instruction, InstructionIterator it, DexItemFactory dexItemFactory) {
     if (!instruction.isNewInstance()
         || instruction.asNewInstance().clazz != dexItemFactory.npeType) {
       return false;
@@ -1708,7 +1697,7 @@
    * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
    */
   private static boolean alwaysTriggerExpectedEffectBeforeAnythingElse(
-      IRCode code, BiFunction<Instruction, InstructionListIterator, InstructionEffect> function) {
+      IRCode code, BiFunction<Instruction, InstructionIterator, InstructionEffect> function) {
     final int color = code.reserveMarkingColor();
     try {
       ArrayDeque<BasicBlock> worklist = new ArrayDeque<>();
@@ -1721,7 +1710,7 @@
         assert currentBlock.isMarked(color);
 
         InstructionEffect result = InstructionEffect.NO_EFFECT;
-        InstructionListIterator it = currentBlock.listIterator();
+        InstructionIterator it = currentBlock.iterator();
         while (result == InstructionEffect.NO_EFFECT && it.hasNext()) {
           result = function.apply(it.next(), it);
         }
@@ -1810,7 +1799,7 @@
       if (blocksToBeRemoved.contains(block)) {
         continue;
       }
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
@@ -1960,7 +1949,7 @@
       }
     }
 
-    InstructionIterator iterator = code.instructionIterator();
+    InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
@@ -1998,7 +1987,7 @@
   }
 
   private Value blockWithSingleConstNumberAndGoto(BasicBlock block) {
-    InstructionIterator iterator  = block.iterator();
+    InstructionIterator iterator = block.iterator();
     Instruction constNumber = iterator.nextUntil(this::isNotDebugInstruction);
     if (constNumber == null || !constNumber.isConstNumber()) {
       return null;
@@ -2008,7 +1997,7 @@
   }
 
   private Value blockWithAssertionsDisabledFieldPut(BasicBlock block) {
-    InstructionIterator iterator  = block.iterator();
+    InstructionIterator iterator = block.iterator();
     Instruction fieldPut = iterator.nextUntil(this::isNotDebugInstruction);
     return fieldPut != null
         && isAssertionsDisabledFieldPut(fieldPut) ? fieldPut.inValues().get(0) : null;
@@ -2082,7 +2071,7 @@
     // removal.
     TypeAnalysis typeAnalysis = new TypeAnalysis(appView);
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    InstructionIterator it = code.instructionIterator();
+    InstructionListIterator it = code.instructionListIterator();
     boolean needToRemoveTrivialPhis = false;
     while (it.hasNext()) {
       Instruction current = it.next();
@@ -2117,7 +2106,7 @@
 
   // Returns true if the given check-cast instruction was removed.
   private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial(
-      CheckCast checkCast, InstructionIterator it, IRCode code, Set<Value> affectedValues) {
+      CheckCast checkCast, InstructionListIterator it, IRCode code, Set<Value> affectedValues) {
     Value inValue = checkCast.object();
     Value outValue = checkCast.outValue();
     DexType castType = checkCast.getType();
@@ -2179,7 +2168,7 @@
 
   // Returns true if the given instance-of instruction was removed.
   private boolean removeInstanceOfInstructionIfTrivial(
-      InstanceOf instanceOf, InstructionIterator it, IRCode code) {
+      InstanceOf instanceOf, InstructionListIterator it, IRCode code) {
     // If the instance-of type is not accessible in the current context, we should not remove the
     // instance-of instruction in order to preserve IllegalAccessError.
     if (!isTypeVisibleFromContext(appView, code.method.method.holder, instanceOf.type())) {
@@ -2261,7 +2250,7 @@
   }
 
   public static void removeOrReplaceByDebugLocalWrite(
-      Instruction currentInstruction, InstructionIterator it, Value inValue, Value outValue) {
+      Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) {
     if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) {
       DebugLocalWrite debugLocalWrite = new DebugLocalWrite(outValue, inValue);
       it.replaceCurrentInstruction(debugLocalWrite);
@@ -2286,7 +2275,7 @@
   // of register needed (and thereby code size as well).
   public void splitRangeInvokeConstants(IRCode code) {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction current = it.next();
         if (current.isInvoke() && current.asInvoke().requiredArgumentRegisters() > 5) {
@@ -2325,7 +2314,7 @@
    */
   public void useDedicatedConstantForLitInstruction(IRCode code) {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator instructionIterator = block.listIterator();
+      InstructionListIterator instructionIterator = block.listIterator(code);
       while (instructionIterator.hasNext()) {
         Instruction currentInstruction = instructionIterator.next();
         if (shouldBeLitInstruction(currentInstruction)) {
@@ -2471,7 +2460,7 @@
         for (Instruction instruction : instructions) {
           if (instruction.outValue().numberOfPhiUsers() != 0 || instruction.isConstString()) {
             // Add constant into the dominator block of usages.
-            insertConstantInBlock(instruction, block);
+            insertConstantInBlock(instruction, block, code);
           } else {
             assert instruction.isConstNumber();
             ConstNumber constNumber = instruction.asConstNumber();
@@ -2481,7 +2470,7 @@
             for (Instruction user : constantValue.uniqueUsers()) {
               ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
               newCstNum.setPosition(user.getPosition());
-              InstructionListIterator iterator = user.getBlock().listIterator(user);
+              InstructionListIterator iterator = user.getBlock().listIterator(code, user);
               iterator.previous();
               iterator.add(newCstNum);
               user.replaceValue(constantValue, newCstNum.outValue());
@@ -2492,7 +2481,7 @@
       } else {
         // Add constant into the dominator block of usages.
         for (Instruction instruction : instructions) {
-          insertConstantInBlock(instruction, block);
+          insertConstantInBlock(instruction, block, code);
         }
       }
     }
@@ -2506,10 +2495,7 @@
       Supplier<DominatorTree> dominatorTreeMemoization,
       Map<BasicBlock, List<Instruction>> addConstantInBlock,
       Predicate<ConstInstruction> selector) {
-
-    InstructionListIterator it = block.listIterator();
-    while (it.hasNext()) {
-      Instruction next = it.next();
+    for (Instruction next : block.getInstructions()) {
       if (!next.isConstInstruction()) {
         continue;
       }
@@ -2565,9 +2551,9 @@
     }
   }
 
-  private void insertConstantInBlock(Instruction instruction, BasicBlock block) {
+  private void insertConstantInBlock(Instruction instruction, BasicBlock block, IRCode code) {
     boolean hasCatchHandlers = block.hasCatchHandlers();
-    InstructionListIterator insertAt = block.listIterator();
+    InstructionListIterator insertAt = block.listIterator(code);
     // Place the instruction as late in the block as we can. It needs to go before users
     // and if we have catch handlers it needs to be placed before the throwing instruction.
     insertAt.nextUntil(i ->
@@ -2618,7 +2604,7 @@
     Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
     // We allow the array instantiations to cross block boundaries as long as it hasn't encountered
     // an instruction instance that can throw an exception.
-    InstructionListIterator it = block.listIterator();
+    InstructionIterator it = block.iterator();
     it.nextUntil(i -> i == newArray);
     do {
       visitedBlocks.add(block);
@@ -2659,7 +2645,7 @@
       }
       BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
       block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
-      it = block != null ? block.listIterator() : null;
+      it = block != null ? block.iterator() : null;
     } while (it != null);
     return null;
   }
@@ -2697,7 +2683,7 @@
       Map<Value, Instruction> instructionToInsertForArray = new HashMap<>();
       Map<Value, Integer> storesToRemoveForArray = new HashMap<>();
       // First pass: identify candidates and insert fill array data instruction.
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.getLocalInfo() != null
@@ -2758,7 +2744,7 @@
         Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
         do {
           visitedBlocks.add(block);
-          it = block.listIterator();
+          it = block.listIterator(code);
           while (it.hasNext()) {
             Instruction instruction = it.next();
             if (instruction.isArrayPut()) {
@@ -2800,10 +2786,8 @@
         && !from.getPosition().equals(to.getPosition())) {
       return true;
     }
-    InstructionListIterator iterator = from.getBlock().listIterator(from);
     Position position = null;
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
+    for (Instruction instruction : from.getBlock().instructionsAfter(from)) {
       if (position == null) {
         if (instruction.getPosition().isSome()) {
           position = instruction.getPosition();
@@ -2830,12 +2814,12 @@
         if (!phi.hasLocalInfo() && phi.numberOfUsers() == 1 && phi.numberOfAllUsers() == 1) {
           Instruction instruction = phi.singleUniqueUser();
           if (instruction.isDebugLocalWrite()) {
-            removeDebugWriteOfPhi(phi, instruction.asDebugLocalWrite());
+            removeDebugWriteOfPhi(code, phi, instruction.asDebugLocalWrite());
           }
         }
       }
 
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction prevInstruction = iterator.peekPrevious();
         Instruction instruction = iterator.next();
@@ -2868,9 +2852,10 @@
     }
   }
 
-  private void removeDebugWriteOfPhi(Phi phi, DebugLocalWrite write) {
+  private void removeDebugWriteOfPhi(IRCode code, Phi phi, DebugLocalWrite write) {
     assert write.src() == phi;
-    for (InstructionListIterator iterator = phi.getBlock().listIterator(); iterator.hasNext(); ) {
+    InstructionListIterator iterator = phi.getBlock().listIterator(code);
+    while (iterator.hasNext()) {
       Instruction next = iterator.next();
       if (!next.isDebugLocalWrite()) {
         // If the debug write is not in the block header bail out.
@@ -2892,11 +2877,9 @@
 
   private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
 
-    private final IRCode code;
     private final InternalOptions options;
 
-    private CSEExpressionEquivalence(IRCode code, InternalOptions options) {
-      this.code = code;
+    private CSEExpressionEquivalence(InternalOptions options) {
       this.options = options;
     }
 
@@ -2999,9 +2982,8 @@
 
   private boolean hasCSECandidate(IRCode code, int noCandidate) {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext()) {
-        if (isCSEInstructionCandidate(iterator.next())) {
+      for (Instruction instruction : block.getInstructions()) {
+        if (isCSEInstructionCandidate(instruction)) {
           return true;
         }
       }
@@ -3015,14 +2997,14 @@
     if (hasCSECandidate(code, noCandidate)) {
       final ListMultimap<Wrapper<Instruction>, Value> instructionToValue =
           ArrayListMultimap.create();
-      final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence(code, options);
+      final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence(options);
       final DominatorTree dominatorTree = new DominatorTree(code);
       for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) {
         BasicBlock block = dominatorTree.getSortedBlocks()[i];
         if (block.isMarked(noCandidate)) {
           continue;
         }
-        InstructionListIterator iterator = block.listIterator();
+        InstructionListIterator iterator = block.listIterator(code);
         while (iterator.hasNext()) {
           Instruction instruction = iterator.next();
           if (isCSEInstructionCandidate(instruction)) {
@@ -3057,8 +3039,8 @@
         continue;
       }
       if (block.exit().isIf()) {
-        flipIfBranchesIfNeeded(block);
-        rewriteIfWithConstZero(block);
+        flipIfBranchesIfNeeded(code, block);
+        rewriteIfWithConstZero(code, block);
 
         if (simplifyKnownBooleanCondition(code, block)) {
           continue;
@@ -3250,10 +3232,11 @@
 
       if (ifInstruction.isZeroTest()) {
         changed |=
-            replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, dominatorTree);
+            replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree);
         if (lhs.knownToBeBoolean()) {
           changed |=
-              replaceDominatedConstNumbers(1, lhs, falseTarget, constantsByValue, dominatorTree);
+              replaceDominatedConstNumbers(
+                  1, lhs, falseTarget, constantsByValue, code, dominatorTree);
         }
       } else {
         assert rhs != null;
@@ -3261,22 +3244,42 @@
           ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber();
           changed |=
               replaceDominatedConstNumbers(
-                  lhsAsNumber.getRawValue(), rhs, trueTarget, constantsByValue, dominatorTree);
+                  lhsAsNumber.getRawValue(),
+                  rhs,
+                  trueTarget,
+                  constantsByValue,
+                  code,
+                  dominatorTree);
           if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
             changed |=
                 replaceDominatedConstNumbers(
-                    negateBoolean(lhsAsNumber), rhs, falseTarget, constantsByValue, dominatorTree);
+                    negateBoolean(lhsAsNumber),
+                    rhs,
+                    falseTarget,
+                    constantsByValue,
+                    code,
+                    dominatorTree);
           }
         } else {
           assert rhs.isConstNumber();
           ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber();
           changed |=
               replaceDominatedConstNumbers(
-                  rhsAsNumber.getRawValue(), lhs, trueTarget, constantsByValue, dominatorTree);
+                  rhsAsNumber.getRawValue(),
+                  lhs,
+                  trueTarget,
+                  constantsByValue,
+                  code,
+                  dominatorTree);
           if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
             changed |=
                 replaceDominatedConstNumbers(
-                    negateBoolean(rhsAsNumber), lhs, falseTarget, constantsByValue, dominatorTree);
+                    negateBoolean(rhsAsNumber),
+                    lhs,
+                    falseTarget,
+                    constantsByValue,
+                    code,
+                    dominatorTree);
           }
         }
       }
@@ -3328,6 +3331,7 @@
       Value newValue,
       BasicBlock dominator,
       Supplier<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
+      IRCode code,
       Supplier<DominatorTree> dominatorTree) {
     if (newValue.hasLocalInfo()) {
       // We cannot replace a constant with a value that has local info, because that could change
@@ -3378,7 +3382,7 @@
       if (dominatorTree.get().dominatedBy(block, dominator)) {
         if (newValue.getTypeLattice().lessThanOrEqual(value.getTypeLattice(), appView)) {
           value.replaceUsers(newValue);
-          block.listIterator(constNumber).removeOrReplaceByDebugLocalRead();
+          block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
           constantWithValueIterator.remove();
           changed = true;
         } else if (value.getTypeLattice().isNullType()) {
@@ -3410,7 +3414,7 @@
       if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
         continue;
       }
-      InstructionListIterator insnIterator = block.listIterator();
+      InstructionListIterator insnIterator = block.listIterator(code);
       while (insnIterator.hasNext()) {
         Instruction insn = insnIterator.next();
         if (!insn.isInvokeMethod()) {
@@ -3443,7 +3447,7 @@
         assert gotoInsn.isGoto();
         assert insnIterator.hasNext();
         BasicBlock throwNullBlock = insnIterator.split(code, blockIterator);
-        InstructionListIterator throwNullInsnIterator = throwNullBlock.listIterator();
+        InstructionListIterator throwNullInsnIterator = throwNullBlock.listIterator(code);
 
         // Insert 'null' constant.
         ConstNumber nullConstant = code.createConstNull(gotoInsn.getLocalInfo());
@@ -3582,9 +3586,11 @@
         if (firstInstruction.isDebugPosition()) {
           assert b.getPredecessors().size() == 1;
           BasicBlock predecessorBlock = b.getPredecessors().get(0);
-          InstructionListIterator it = predecessorBlock.listIterator(predecessorBlock.exit());
+          InstructionIterator it = predecessorBlock.iterator(predecessorBlock.exit());
           Instruction previousPosition = null;
-          while (it.hasPrevious() && !(previousPosition = it.previous()).isDebugPosition());
+          while (it.hasPrevious() && !(previousPosition = it.previous()).isDebugPosition()) {
+            // Intentionally empty.
+          }
           if (previousPosition != null) {
             return previousPosition.getPosition() == firstInstruction.getPosition();
           }
@@ -3599,12 +3605,12 @@
       IRCode code, BasicBlock block, If theIf, BasicBlock target, BasicBlock deadTarget) {
     deadTarget.unlinkSinglePredecessorSiblingsAllowed();
     assert theIf == block.exit();
-    block.replaceLastInstruction(new Goto());
+    block.replaceLastInstruction(new Goto(), code);
     assert block.exit().isGoto();
     assert block.exit().asGoto().getTarget() == target;
   }
 
-  private void rewriteIfWithConstZero(BasicBlock block) {
+  private void rewriteIfWithConstZero(IRCode code, BasicBlock block) {
     If theIf = block.exit().asIf();
     if (theIf.isZeroTest()) {
       return;
@@ -3617,20 +3623,20 @@
       if (leftValue.isConstNumber()) {
         if (leftValue.getConstInstruction().asConstNumber().isZero()) {
           If ifz = new If(theIf.getType().forSwappedOperands(), rightValue);
-          block.replaceLastInstruction(ifz);
+          block.replaceLastInstruction(ifz, code);
           assert block.exit() == ifz;
         }
       } else {
         if (rightValue.getConstInstruction().asConstNumber().isZero()) {
           If ifz = new If(theIf.getType(), leftValue);
-          block.replaceLastInstruction(ifz);
+          block.replaceLastInstruction(ifz, code);
           assert block.exit() == ifz;
         }
       }
     }
   }
 
-  private boolean flipIfBranchesIfNeeded(BasicBlock block) {
+  private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) {
     If theIf = block.exit().asIf();
     BasicBlock trueTarget = theIf.getTrueTarget();
     BasicBlock fallthrough = theIf.fallthroughBlock();
@@ -3646,13 +3652,13 @@
     // on older Android versions.
     List<Value> inValues = theIf.inValues();
     If newIf = new If(theIf.getType().inverted(), inValues);
-    block.replaceLastInstruction(newIf);
+    block.replaceLastInstruction(newIf, code);
     block.swapSuccessors(trueTarget, fallthrough);
     return true;
   }
 
   public void rewriteConstantEnumMethodCalls(IRCode code) {
-    InstructionIterator iterator = code.instructionIterator().recordChangesToMetadata(code);
+    InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
 
@@ -3723,7 +3729,7 @@
       return;
     }
 
-    InstructionIterator iterator = code.instructionIterator();
+    InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
@@ -3756,7 +3762,7 @@
       return;
     }
 
-    InstructionIterator iterator = code.instructionIterator();
+    InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
@@ -3829,7 +3835,7 @@
       }
       Int2IntMap previousMapping = new Int2IntOpenHashMap();
       Int2IntMap mapping = new Int2IntOpenHashMap();
-      ListIterator<Instruction> it = block.getInstructions().listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isMove()) {
@@ -3839,7 +3845,7 @@
             int src = allocator.getRegisterForValue(move.src(), move.getNumber());
             int mappedSrc = mapping.getOrDefault(src, src);
             mapping.put(dst, mappedSrc);
-            it.remove();
+            it.removeInstructionIgnoreOutValue();
           }
         } else if (instruction.isDebugLocalsChange()) {
           DebugLocalsChange change = instruction.asDebugLocalsChange();
@@ -3861,7 +3867,7 @@
     IntSet clobberedRegisters = new IntOpenHashSet();
     // Backwards instruction scan collecting the registers used by actual instructions.
     boolean inEntrySpillMoves = false;
-    InstructionListIterator it = block.listIterator(block.getInstructions().size());
+    InstructionIterator it = block.iterator(block.getInstructions().size());
     while (it.hasPrevious()) {
       Instruction instruction = it.previous();
       if (instruction == postSpillLocalsChange) {
@@ -3923,7 +3929,7 @@
     ThrowableMethods throwableMethods = dexItemFactory.throwableMethods;
 
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
@@ -4001,7 +4007,7 @@
   public void logArgumentTypes(DexEncodedMethod method, IRCode code) {
     List<Value> arguments = code.collectArguments();
     BasicBlock block = code.entryBlock();
-    InstructionListIterator iterator = block.listIterator().recordChangesToMetadata(code);
+    InstructionListIterator iterator = block.listIterator(code);
 
     // Attach some synthetic position to all inserted code.
     Position position = Position.synthetic(1, method.method, null);
@@ -4079,10 +4085,10 @@
         isNullBlock.link(successor);
 
         // Fill code into the blocks.
-        iterator = isNullBlock.listIterator();
+        iterator = isNullBlock.listIterator(code);
         iterator.setInsertionPosition(position);
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
-        iterator = isNotNullBlock.listIterator();
+        iterator = isNotNullBlock.listIterator(code);
         iterator.setInsertionPosition(position);
         value = code.createValue(TypeLatticeElement.classClassType(appView, definitelyNotNull()));
         iterator.add(new InvokeVirtual(dexItemFactory.objectMethods.getClass, value,
@@ -4090,7 +4096,7 @@
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
       }
 
-      iterator = eol.listIterator();
+      iterator = eol.listIterator(code);
       iterator.setInsertionPosition(position);
       if (i == arguments.size() - 1) {
         iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, closeParenthesis)));
@@ -4104,23 +4110,20 @@
   }
 
   public static void ensureDirectStringNewToInit(IRCode code, DexItemFactory dexItemFactory) {
-    for (BasicBlock block : code.blocks) {
-      for (InstructionListIterator it = block.listIterator(); it.hasNext(); ) {
-        Instruction instruction = it.next();
-        if (instruction.isInvokeDirect()) {
-          InvokeDirect invoke = instruction.asInvokeDirect();
-          DexMethod method = invoke.getInvokedMethod();
-          if (dexItemFactory.isConstructor(method)
-              && method.holder == dexItemFactory.stringType
-              && invoke.getReceiver().isPhi()) {
-            NewInstance newInstance = findNewInstance(invoke.getReceiver().asPhi());
-            replaceTrivialNewInstancePhis(newInstance.outValue());
-            if (invoke.getReceiver().isPhi()) {
-              throw new CompilationError(
-                  "Failed to remove trivial phis between new-instance and <init>");
-            }
-            newInstance.markNoSpilling();
+    for (Instruction instruction : code.instructions()) {
+      if (instruction.isInvokeDirect()) {
+        InvokeDirect invoke = instruction.asInvokeDirect();
+        DexMethod method = invoke.getInvokedMethod();
+        if (dexItemFactory.isConstructor(method)
+            && method.holder == dexItemFactory.stringType
+            && invoke.getReceiver().isPhi()) {
+          NewInstance newInstance = findNewInstance(invoke.getReceiver().asPhi());
+          replaceTrivialNewInstancePhis(newInstance.outValue());
+          if (invoke.getReceiver().isPhi()) {
+            throw new CompilationError(
+                "Failed to remove trivial phis between new-instance and <init>");
           }
+          newInstance.markNoSpilling();
         }
       }
     }
@@ -4257,7 +4260,7 @@
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isArithmeticBinop() || instruction.isNeg()) {
@@ -4280,12 +4283,12 @@
                   block.hasCatchHandlers() ? it.split(code, blocks) : block;
               if (blockWithInvokeNaN != block) {
                 // If we split, add the invoke at the end of the original block.
-                it = block.listIterator(block.getInstructions().size());
+                it = block.listIterator(code, block.getInstructions().size());
                 it.previous();
                 it.add(invokeIsNaN);
                 // Continue iteration in the split block.
                 block = blockWithInvokeNaN;
-                it = block.listIterator();
+                it = block.listIterator(code);
               } else {
                 // Otherwise, add it to the current block.
                 it.add(invokeIsNaN);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 00b4540..04bdf84 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -99,46 +99,43 @@
             });
 
     // Collect usages of constants that can be canonicalized.
-    for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
-      while (it.hasNext()) {
-        Instruction current = it.next();
-        // Interested in ConstNumber, (DexItemBased)?ConstString, and ConstClass
-        if (!current.isConstNumber()
-            && !current.isConstString()
-            && !current.isDexItemBasedConstString()
-            && !current.isConstClass()) {
-          continue;
-        }
-        // Do not canonicalize ConstClass that may have side effects. Its original instructions
-        // will not be removed by dead code remover due to the side effects.
-        if (current.isConstClass()
-            && current.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
-          continue;
-        }
-        // Do not canonicalize ConstString instructions if there are monitor operations in the code.
-        // That could lead to unbalanced locking and could lead to situations where OOM exceptions
-        // could leave a synchronized method without unlocking the monitor.
-        if ((current.isConstString() || current.isDexItemBasedConstString())
-            && code.metadata().mayHaveMonitorInstruction()) {
-          continue;
-        }
-        // Constants with local info must not be canonicalized and must be filtered.
-        if (current.outValue().hasLocalInfo()) {
-          continue;
-        }
-        // Constants that are used by invoke range are not canonicalized to be compliant with the
-        // optimization splitRangeInvokeConstant that gives the register allocator more freedom in
-        // assigning register to ranged invokes which can greatly reduce the number of register
-        // needed (and thereby code size as well). Thus no need to do a transformation that should
-        // be removed later by another optimization.
-        if (constantUsedByInvokeRange(current.asConstInstruction())) {
-          continue;
-        }
-        List<Value> oldValuesDefinedByConstant = valuesDefinedByConstant.computeIfAbsent(
-            current.asConstInstruction(), k -> new ArrayList<>());
-        oldValuesDefinedByConstant.add(current.outValue());
+    for (Instruction current : code.instructions()) {
+      // Interested in ConstNumber, (DexItemBased)?ConstString, and ConstClass
+      if (!current.isConstNumber()
+          && !current.isConstString()
+          && !current.isDexItemBasedConstString()
+          && !current.isConstClass()) {
+        continue;
       }
+      // Do not canonicalize ConstClass that may have side effects. Its original instructions
+      // will not be removed by dead code remover due to the side effects.
+      if (current.isConstClass()
+          && current.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
+        continue;
+      }
+      // Do not canonicalize ConstString instructions if there are monitor operations in the code.
+      // That could lead to unbalanced locking and could lead to situations where OOM exceptions
+      // could leave a synchronized method without unlocking the monitor.
+      if ((current.isConstString() || current.isDexItemBasedConstString())
+          && code.metadata().mayHaveMonitorInstruction()) {
+        continue;
+      }
+      // Constants with local info must not be canonicalized and must be filtered.
+      if (current.outValue().hasLocalInfo()) {
+        continue;
+      }
+      // Constants that are used by invoke range are not canonicalized to be compliant with the
+      // optimization splitRangeInvokeConstant that gives the register allocator more freedom in
+      // assigning register to ranged invokes which can greatly reduce the number of register
+      // needed (and thereby code size as well). Thus no need to do a transformation that should
+      // be removed later by another optimization.
+      if (constantUsedByInvokeRange(current.asConstInstruction())) {
+        continue;
+      }
+      List<Value> oldValuesDefinedByConstant =
+          valuesDefinedByConstant.computeIfAbsent(
+              current.asConstInstruction(), k -> new ArrayList<>());
+      oldValuesDefinedByConstant.add(current.outValue());
     }
 
     if (valuesDefinedByConstant.isEmpty()) {
@@ -210,7 +207,7 @@
     // Insert the constant instruction at the start of the block right after the argument
     // instructions. It is important that the const instruction is put before any instruction
     // that can throw exceptions (since the value could be used on the exceptional edge).
-    InstructionListIterator it = entryBlock.listIterator();
+    InstructionListIterator it = entryBlock.listIterator(code);
     while (it.hasNext()) {
       if (!it.next().isArgument()) {
         it.previous();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 16a5d2a..8dc27fd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -86,7 +86,7 @@
   }
 
   private void removeDeadInstructions(Queue<BasicBlock> worklist, IRCode code, BasicBlock block) {
-    InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
+    InstructionListIterator iterator = block.listIterator(code, block.getInstructions().size());
     while (iterator.hasPrevious()) {
       Instruction current = iterator.previous();
       // Remove unused invoke results.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 998de0d..bebbbf4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -52,7 +52,7 @@
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction current = it.next();
 
@@ -191,13 +191,13 @@
                   block.hasCatchHandlers() ? it.split(code, blocks) : block;
               if (blockWithDevirtualizedInvoke != block) {
                 // If we split, add the new checkcast at the end of the currently visiting block.
-                it = block.listIterator(block.getInstructions().size());
+                it = block.listIterator(code, block.getInstructions().size());
                 it.previous();
                 it.add(checkCast);
                 // Update the dominator tree after the split.
                 dominatorTree = new DominatorTree(code);
                 // Restore the cursor.
-                it = blockWithDevirtualizedInvoke.listIterator();
+                it = blockWithDevirtualizedInvoke.listIterator(code);
                 assert it.peekNext() == devirtualizedInvoke;
                 it.next();
               } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 97e0417..ace7a3b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -52,7 +52,7 @@
   //  instructions.
   private void insertAssumeDynamicTypeInstructionsInBlock(
       IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
-    InstructionListIterator instructionIterator = block.listIterator();
+    InstructionListIterator instructionIterator = block.listIterator(code);
     while (instructionIterator.hasNext()) {
       Instruction current = instructionIterator.next();
       if (current.isInvokeMethod()) {
@@ -103,7 +103,7 @@
         if (insertionBlock == block) {
           instructionIterator.add(assumeInstruction);
         } else {
-          insertionBlock.listIterator().add(assumeInstruction);
+          insertionBlock.listIterator(code).add(assumeInstruction);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
index 3f02176..2472589 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,9 +54,7 @@
     DexEncodedMethod initializer = clazz.getClassInitializer();
     IRCode code = initializer.getCode().buildIR(initializer, appView, clazz.origin);
     Map<DexField, EnumValueInfo> valueInfoMap = new IdentityHashMap<>();
-    InstructionIterator it = code.instructionIterator();
-    while (it.hasNext()) {
-      Instruction insn = it.next();
+    for (Instruction insn : code.instructions()) {
       if (!insn.isStaticPut()) {
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 29392f4..0195c84 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -52,6 +52,7 @@
  *   // Update users of vx, vy, and vz.
  */
 public class IdempotentFunctionCallCanonicalizer {
+
   // Threshold to limit the number of invocation canonicalization.
   private static final int MAX_CANONICALIZED_CALL = 15;
 
@@ -80,10 +81,14 @@
         "# invoke canonicalization (program): %s", numberOfProgramCallCanonicalization);
     assert histogramOfCanonicalizationCandidatesPerMethod != null;
     Log.info(getClass(), "------ histogram of invoke canonicalization candidates ------");
-    histogramOfCanonicalizationCandidatesPerMethod.forEach((length, count) -> {
-      Log.info(getClass(),
-          "%s: %s (%s)", length, StringUtils.times("*", Math.min(count, 53)), count);
-    });
+    histogramOfCanonicalizationCandidatesPerMethod.forEach(
+        (length, count) ->
+            Log.info(
+                getClass(),
+                "%s: %s (%s)",
+                length,
+                StringUtils.times("*", Math.min(count, 53)),
+                count));
   }
 
   public void canonicalize(IRCode code) {
@@ -108,9 +113,7 @@
     DexType context = code.method.method.holder;
     // Collect invocations along with arguments.
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
-      while (it.hasNext()) {
-        Instruction current = it.next();
+      for (Instruction current : block.getInstructions()) {
         if (!current.isInvokeMethod()) {
           continue;
         }
@@ -228,7 +231,7 @@
 
     if (!deadInvocations.isEmpty()) {
       for (BasicBlock block : code.blocks) {
-        InstructionListIterator it = block.listIterator();
+        InstructionListIterator it = block.listIterator(code);
         while (it.hasNext()) {
           Instruction current = it.next();
           if (!current.isInvokeMethod()) {
@@ -264,7 +267,7 @@
     BasicBlock entryBlock = code.entryBlock();
     // Insert the canonicalized invoke after in values.
     int numberOfInValuePassed = 0;
-    InstructionListIterator it = entryBlock.listIterator();
+    InstructionListIterator it = entryBlock.listIterator(code);
     while (it.hasNext()) {
       Instruction current = it.next();
       if (current.hasOutValue() && canonicalizedInvoke.inValues().contains(current.outValue())) {
@@ -287,7 +290,7 @@
     BasicBlock entryBlock = code.entryBlock();
     // Insert the canonicalized invocation at the start of the block right after the argument
     // instructions.
-    InstructionListIterator it = entryBlock.listIterator();
+    InstructionListIterator it = entryBlock.listIterator(code);
     while (it.hasNext()) {
       if (!it.next().isArgument()) {
         it.previous();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index f91ac1a..b1f231e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -106,16 +106,14 @@
     }
 
     if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
-        && returnsIntAsBoolean(code, method, appView)) {
+        && returnsIntAsBoolean(code, method)) {
       return ConstraintWithTarget.NEVER;
     }
 
     ConstraintWithTarget result = ConstraintWithTarget.ALWAYS;
     InliningConstraints inliningConstraints =
         new InliningConstraints(appView, GraphLense.getIdentityLense());
-    InstructionIterator it = code.instructionIterator();
-    while (it.hasNext()) {
-      Instruction instruction = it.next();
+    for (Instruction instruction : code.instructions()) {
       ConstraintWithTarget state =
           instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder);
       if (state == ConstraintWithTarget.NEVER) {
@@ -128,12 +126,12 @@
     return result;
   }
 
-  private boolean returnsIntAsBoolean(IRCode code, DexEncodedMethod method, AppView appView) {
+  private boolean returnsIntAsBoolean(IRCode code, DexEncodedMethod method) {
     DexType returnType = method.method.proto.returnType;
     for (BasicBlock basicBlock : code.blocks) {
-      InstructionListIterator instructionListIterator = basicBlock.listIterator();
-      while (instructionListIterator.hasNext()) {
-        Instruction instruction = instructionListIterator.nextUntil(Instruction::isReturn);
+      InstructionIterator instructionIterator = basicBlock.iterator();
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.nextUntil(Instruction::isReturn);
         if (instruction != null) {
           if (returnType.isBooleanType() && !instruction.inValues().get(0).knownToBeBoolean()) {
             return true;
@@ -576,7 +574,8 @@
 
             // Insert a new block between the last argument instruction and the first actual
             // instruction of the method.
-            BasicBlock throwBlock = entryBlock.listIterator(arguments.size()).split(code, 0, null);
+            BasicBlock throwBlock =
+                entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
             assert !throwBlock.hasCatchHandlers();
 
             // Link the entry block to the successor of the newly inserted block.
@@ -587,12 +586,12 @@
             // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
             // is null.
             If ifInstruction = new If(If.Type.EQ, receiver);
-            entryBlock.replaceLastInstruction(ifInstruction);
+            entryBlock.replaceLastInstruction(ifInstruction, code);
             assert ifInstruction.getTrueTarget() == throwBlock;
             assert ifInstruction.fallthroughBlock() == continuationBlock;
 
             // Replace the single goto instruction in the newly inserted block by `throw null`.
-            InstructionListIterator iterator = throwBlock.listIterator();
+            InstructionListIterator iterator = throwBlock.listIterator(code);
             Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
             iterator.next();
             iterator.replaceCurrentInstruction(new Throw(nullValue));
@@ -804,7 +803,7 @@
       if (blocksToRemove.contains(block)) {
         continue;
       }
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 2326b45..813b6e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -233,7 +233,7 @@
       }
       replacement.setPosition(current.getPosition());
       if (current.getBlock().hasCatchHandlers()) {
-        iterator.split(code, blocks).listIterator().recordChangesToMetadata(code).add(replacement);
+        iterator.split(code, blocks).listIterator(code).add(replacement);
       } else {
         iterator.add(replacement);
       }
@@ -315,7 +315,7 @@
       replacement.setPosition(current.getPosition());
       current.moveDebugValues(replacement);
       if (current.getBlock().hasCatchHandlers()) {
-        iterator.split(code, blocks).listIterator().recordChangesToMetadata(code).add(replacement);
+        iterator.split(code, blocks).listIterator(code).add(replacement);
       } else {
         iterator.add(replacement);
       }
@@ -457,7 +457,7 @@
             nonNullValue, knownToBeNonNullValue, current, appView);
     nonNull.setPosition(appView.options().debug ? current.getPosition() : Position.none());
     if (current.getBlock().hasCatchHandlers()) {
-      iterator.split(code, blocks).listIterator().recordChangesToMetadata(code).add(nonNull);
+      iterator.split(code, blocks).listIterator(code).add(nonNull);
     } else {
       iterator.add(nonNull);
     }
@@ -475,7 +475,7 @@
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator iterator = block.listIterator().recordChangesToMetadata(code);
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
index bae9b27..c6694f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -43,7 +43,7 @@
   public static void rewriteNestCallsForInlining(
       IRCode code, DexType callerHolder, AppView<?> appView) {
     // This method is called when inlining code into the nest member callerHolder.
-    InstructionIterator iterator = code.instructionIterator();
+    InstructionListIterator iterator = code.instructionListIterator();
     DexClass callerHolderClass = appView.definitionFor(callerHolder);
     assert callerHolderClass != null;
     assert code.method.method.holder != callerHolder;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 07f6039..84247d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
@@ -69,7 +70,7 @@
       // 1) invocations that call non-overridable library methods that are known to return non null.
       // 2) instructions that implicitly indicate receiver/array is not null.
       // 3) parameters that are not null after the invocation.
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()
@@ -201,7 +202,7 @@
                 Assume<NonNullAssumption> nonNull =
                     Assume.createAssumeNonNullInstruction(
                         nonNullValue, knownToBeNonNullValue, theIf, appView);
-                InstructionListIterator targetIterator = target.listIterator();
+                InstructionListIterator targetIterator = target.listIterator(code);
                 nonNull.setPosition(targetIterator.next().getPosition());
                 targetIterator.previous();
                 targetIterator.add(nonNull);
@@ -261,7 +262,7 @@
       Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
       for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
         dominatedBlocks.add(dominatee);
-        InstructionListIterator dominateeIterator = dominatee.listIterator();
+        InstructionIterator dominateeIterator = dominatee.iterator();
         if (dominatee == blockWithNonNullInstruction && !split) {
           // In the block where the non null instruction will be inserted, skip instructions up to
           // and including the insertion point.
@@ -307,7 +308,7 @@
         nonNull.setPosition(current.getPosition());
         if (blockWithNonNullInstruction != block) {
           // If we split, add non-null IR on top of the new split block.
-          blockWithNonNullInstruction.listIterator().add(nonNull);
+          blockWithNonNullInstruction.listIterator(code).add(nonNull);
         } else {
           // Otherwise, just add it to the current block at the position of the iterator.
           iterator.add(nonNull);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 2fe484e..4837545 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1237,7 +1237,7 @@
           // and none had a position. The code no longer has the previous property.
           code.setAllThrowingInstructionsHavePositions(false);
         }
-        InstructionListIterator endIterator = block.listIterator(end - 1);
+        InstructionListIterator endIterator = block.listIterator(code, end - 1);
         Instruction instructionBeforeEnd = endIterator.next();
         invalidateInstructionArray(); // Because we're about to modify the original linked list.
         instructionBeforeEnd.clearBlock();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index bdd4d7ff..c0a67fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -328,7 +328,7 @@
         || (successorBlock == null && first.exit().isReturn());
     BasicBlock newBlock = new BasicBlock();
     newBlock.setNumber(blockNumber);
-    InstructionListIterator from = first.listIterator(first.getInstructions().size());
+    InstructionIterator from = first.iterator(first.getInstructions().size());
     Int2ReferenceMap<DebugLocalInfo> newBlockEntryLocals = null;
     if (first.getLocalsAtEntry() != null) {
       newBlockEntryLocals = new Int2ReferenceOpenHashMap<>(first.getLocalsAtEntry());
@@ -411,8 +411,8 @@
     if (!Objects.equals(localsAtBlockExit(block0), localsAtBlockExit(block1))) {
       return 0;
     }
-    InstructionListIterator it0 = block0.listIterator(block0.getInstructions().size());
-    InstructionListIterator it1 = block1.listIterator(block1.getInstructions().size());
+    InstructionIterator it0 = block0.iterator(block0.getInstructions().size());
+    InstructionIterator it1 = block1.iterator(block1.getInstructions().size());
     int suffixSize = 0;
     while (it0.hasPrevious() && it1.hasPrevious()) {
       Instruction i0 = it0.previous();
@@ -492,11 +492,11 @@
       // the same register.
       Map<Integer, ConstNumber> registerToNumber = new HashMap<>();
       MoveEliminator moveEliminator = new MoveEliminator(allocator);
-      ListIterator<Instruction> iterator = block.getInstructions().listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (moveEliminator.shouldBeEliminated(current)) {
-          iterator.remove();
+          iterator.removeInstructionIgnoreOutValue();
         } else if (current.outValue() != null && current.outValue().needsRegister()) {
           Value outValue = current.outValue();
           int instructionNumber = current.getNumber();
@@ -504,7 +504,7 @@
             if (constantSpilledAtDefinition(current.asConstNumber())) {
               // Remove constant instructions that are spilled at their definition and are
               // therefore unused.
-              iterator.remove();
+              iterator.removeInstructionIgnoreOutValue();
               continue;
             }
             int outRegister = allocator.getRegisterForValue(outValue, instructionNumber);
@@ -513,7 +513,7 @@
                 && numberInRegister.identicalNonValueNonPositionParts(current)) {
               // This instruction is not needed, the same constant is already in this register.
               // We don't consider the positions of the two (non-throwing) instructions.
-              iterator.remove();
+              iterator.removeInstructionIgnoreOutValue();
             } else {
               // Insert the current constant in the mapping. Make sure to clobber the second
               // register if wide and register-1 if that defines a wide value.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java
index c77171b..9a5e3b9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Load;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Store;
@@ -86,7 +86,7 @@
   private static int getStackHeightAtInstructionBackwards(Instruction instruction) {
     int stackHeight = 0;
     BasicBlock block = instruction.getBlock();
-    InstructionListIterator it = block.listIterator(block.getInstructions().size() - 1);
+    InstructionIterator it = block.iterator(block.getInstructions().size() - 1);
     while (it.hasPrevious()) {
       Instruction current = it.previous();
       if (current == instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 38a1da7..c178221 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -115,7 +115,7 @@
           activeStaticFieldsAtEntry.containsKey(block)
               ? activeStaticFieldsAtEntry.get(block)
               : new IdentityHashMap<>();
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isFieldInstruction()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 7d655ae..9dc651c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -24,7 +24,7 @@
 
   // Rewrite getClass() call to const-class if the type of the given instance is effectively final.
   public static void rewriteGetClass(AppView<AppInfoWithLiveness> appView, IRCode code) {
-    InstructionIterator it = code.instructionIterator();
+    InstructionListIterator it = code.instructionListIterator();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     while (it.hasNext()) {
       Instruction current = it.next();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 6a55792..89e0f2d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -63,7 +63,7 @@
 
   public static void rewrite(IRCode code, AppView<? extends AppInfoWithLiveness> appView) {
     DexItemFactory factory = appView.dexItemFactory();
-    InstructionIterator instructionIterator = code.instructionIterator();
+    InstructionListIterator instructionIterator = code.instructionListIterator();
     while (instructionIterator.hasNext()) {
       Instruction instruction = instructionIterator.next();
 
@@ -188,7 +188,7 @@
     private final IRCode code;
     private final InvokeStatic serviceLoaderLoad;
 
-    private InstructionIterator iterator;
+    private InstructionListIterator iterator;
     private MemberType memberType;
     private Value valueArray;
     private int index = 0;
@@ -196,7 +196,7 @@
     public Rewriter(
         AppView appView,
         IRCode code,
-        InstructionIterator iterator,
+        InstructionListIterator iterator,
         InvokeStatic serviceLoaderLoad) {
       this.appView = appView;
       this.factory = appView.dexItemFactory();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index f6293de..8d5bf96 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -379,7 +379,7 @@
       if (blocksToBeRemoved.contains(block)) {
         continue;
       }
-      InstructionListIterator instructionIterator = block.listIterator();
+      InstructionListIterator instructionIterator = block.listIterator(code);
       while (instructionIterator.hasNext()) {
         Instruction instruction = instructionIterator.next();
         if (instruction.throwsOnNullInput()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
index 4a5b019..43784de 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Value;
@@ -115,8 +115,8 @@
   }
 
   private Value getValueDefinedInTheBlock(BasicBlock block, Instruction stopAt) {
-    InstructionListIterator iterator = stopAt == null ?
-        block.listIterator(block.getInstructions().size()) : block.listIterator(stopAt);
+    InstructionIterator iterator =
+        stopAt == null ? block.iterator(block.getInstructions().size()) : block.iterator(stopAt);
 
     Instruction valueProducingInsn = null;
     while (iterator.hasPrevious()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index 53e0fc9..8d33db8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -182,7 +182,7 @@
   final void processCode() {
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      instructions = block.listIterator();
+      instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         instructions.next().accept(this);
       }
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 eb2df21..b2db648 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
@@ -7,7 +7,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.LinearFlowInstructionIterator;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.ListIterator;
@@ -39,21 +39,22 @@
     while (blocksIterator.hasPrevious()) {
       BasicBlock currentBlock = blocksIterator.previous();
       InstructionListIterator it =
-          new LinearFlowInstructionIterator(currentBlock, currentBlock.getInstructions().size());
+          new LinearFlowInstructionListIterator(
+              code, currentBlock, currentBlock.getInstructions().size());
       boolean matched = false;
       while (matched || it.hasPrevious()) {
         if (!it.hasPrevious()) {
           matched = false;
           it =
-              new LinearFlowInstructionIterator(
-                  currentBlock, currentBlock.getInstructions().size());
+              new LinearFlowInstructionListIterator(
+                  code, currentBlock, currentBlock.getInstructions().size());
         }
         for (BasicBlockPeephole peepHole : peepholes) {
           boolean localMatch = peepHole.match(it);
           if (localMatch && peepHole.resetAfterMatch()) {
             it =
-                new LinearFlowInstructionIterator(
-                    currentBlock, currentBlock.getInstructions().size());
+                new LinearFlowInstructionListIterator(
+                    code, currentBlock, currentBlock.getInstructions().size());
           } else {
             matched |= localMatch;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 60c34a1..4815c1f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Phi;
@@ -278,9 +278,7 @@
     assert candidateInfo != null;
 
     // Find and remove instantiation and its users.
-    InstructionIterator iterator = code.instructionIterator();
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
+    for (Instruction instruction : code.instructions()) {
       if (instruction.isNewInstance() &&
           instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
         // Remove all usages
@@ -293,8 +291,8 @@
 
         Value singletonValue = instruction.outValue();
         assert singletonValue != null;
-        singletonValue.uniqueUsers().forEach(Instruction::removeOrReplaceByDebugLocalRead);
-        instruction.removeOrReplaceByDebugLocalRead();
+        singletonValue.uniqueUsers().forEach(user -> user.removeOrReplaceByDebugLocalRead(code));
+        instruction.removeOrReplaceByDebugLocalRead(code);
         return;
       }
     }
@@ -315,18 +313,19 @@
             .filter(get -> singletonFields.containsKey(get.getField()))
             .collect(Collectors.toList());
 
-    singletonFieldReads.forEach(read -> {
-      DexField field = read.getField();
-      CandidateInfo candidateInfo = singletonFields.get(field);
-      assert candidateInfo != null;
-      Value value = read.dest();
-      if (value != null) {
-        fixupStaticizedFieldReadUsers(code, value, field);
-      }
-      if (!candidateInfo.preserveRead.get()) {
-        read.removeOrReplaceByDebugLocalRead();
-      }
-    });
+    singletonFieldReads.forEach(
+        read -> {
+          DexField field = read.getField();
+          CandidateInfo candidateInfo = singletonFields.get(field);
+          assert candidateInfo != null;
+          Value value = read.dest();
+          if (value != null) {
+            fixupStaticizedFieldReadUsers(code, value, field);
+          }
+          if (!candidateInfo.preserveRead.get()) {
+            read.removeOrReplaceByDebugLocalRead(code);
+          }
+        });
 
     if (!candidateToHostMapping.isEmpty()) {
       remapMovedCandidates(code);
@@ -490,12 +489,12 @@
       List<Value> args = invoke.inValues();
       invoke.replace(
           new InvokeStatic(invoke.getInvokedMethod(), newValue, args.subList(1, args.size())),
-          code.metadata());
+          code);
     }
   }
 
   private void remapMovedCandidates(IRCode code) {
-    InstructionIterator it = code.instructionIterator();
+    InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
       Instruction instruction = it.next();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 152fcd8..7f2faef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.code.DominatorTree.Assumption;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
@@ -354,10 +354,7 @@
     private StringConcatenationAnalysis buildBuilderStateGraph(Set<Value> candidateBuilders) {
       DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
       for (BasicBlock block : code.topologicallySortedBlocks()) {
-        InstructionIterator it = block.listIterator();
-        while (it.hasNext()) {
-          Instruction instr = it.next();
-
+        for (Instruction instr : block.getInstructions()) {
           if (instr.isNewInstance()
               && optimizationConfiguration.isBuilderType(instr.asNewInstance().clazz)) {
             Value builder = instr.asNewInstance().dest();
@@ -506,7 +503,7 @@
     final Set<Value> simplifiedBuilders = Sets.newIdentityHashSet();
 
     private StringConcatenationAnalysis applyConcatenationResults(Set<Value> candidateBuilders) {
-      InstructionIterator it = code.instructionIterator();
+      InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instr = it.nextUntil(i -> isToStringOfInterest(candidateBuilders, i));
         if (instr == null) {
@@ -634,7 +631,7 @@
       }
       // All instructions that refer to simplified builders are dead.
       // Here, we remove append(...) calls, <init>, and new-instance in order.
-      InstructionIterator it = code.instructionIterator();
+      InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instr = it.next();
         if (instr.isInvokeVirtual()) {
@@ -646,7 +643,7 @@
           }
         }
       }
-      it = code.instructionIterator();
+      it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instr = it.next();
         if (instr.isInvokeDirect()) {
@@ -658,7 +655,7 @@
           }
         }
       }
-      it = code.instructionIterator();
+      it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instr = it.next();
         if (instr.isNewInstance()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 3adb9d4..1ccf6df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -26,7 +26,7 @@
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
@@ -66,7 +66,7 @@
     if (!code.metadata().mayHaveConstString()) {
       return;
     }
-    InstructionIterator it = code.instructionIterator();
+    InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
       Instruction instr = it.next();
       if (!instr.isInvokeVirtual()) {
@@ -207,7 +207,7 @@
       return;
     }
     boolean markUseIdentifierNameString = false;
-    InstructionIterator it = code.instructionIterator().recordChangesToMetadata(code);
+    InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
       Instruction instr = it.next();
       if (!instr.isInvokeVirtual()) {
@@ -353,7 +353,7 @@
   // String#valueOf(String s) -> s
   // str.toString() -> str
   public void removeTrivialConversions(IRCode code) {
-    InstructionIterator it = code.instructionIterator().recordChangesToMetadata(code);
+    InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
       Instruction instr = it.next();
       if (instr.isInvokeStatic()) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 1a14543..8456912 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -222,9 +222,9 @@
     // locals alive for their entire live range. In release mode the liveness is all that matters
     // and we do not actually want locals information in the output.
     if (options().debug) {
-      computeDebugInfo(blocks, liveIntervals, this, liveAtEntrySets);
+      computeDebugInfo(code, blocks, liveIntervals, this, liveAtEntrySets);
     } else if (code.method.getOptimizationInfo().isReachabilitySensitive()) {
-      InstructionIterator it = code.instructionIterator();
+      InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isDebugLocalRead()) {
@@ -238,6 +238,7 @@
   }
 
   public static void computeDebugInfo(
+      IRCode code,
       ImmutableList<BasicBlock> blocks,
       List<LiveIntervals> liveIntervals,
       RegisterAllocator allocator,
@@ -278,7 +279,7 @@
 
     boolean isEntryBlock = true;
     for (BasicBlock block : blocks) {
-      InstructionListIterator instructionIterator = block.listIterator();
+      InstructionListIterator instructionIterator = block.listIterator(code);
       Set<Value> liveLocalValues = new HashSet<>(liveAtEntrySets.get(block).liveLocalValues);
       // Skip past arguments and open argument and phi locals.
       if (isEntryBlock) {
@@ -746,7 +747,7 @@
 
   private void removeSpillAndPhiMoves() {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (isSpillInstruction(instruction)) {
@@ -2549,8 +2550,7 @@
         }
         addLiveRange(value, block, end, liveIntervals, options);
       }
-      ListIterator<Instruction> iterator =
-          block.getInstructions().listIterator(block.getInstructions().size());
+      InstructionIterator iterator = block.iterator(block.getInstructions().size());
       while (iterator.hasPrevious()) {
         Instruction instruction = iterator.previous();
         Value definition = instruction.outValue();
@@ -2663,7 +2663,7 @@
   private void transformBridgeMethod() {
     assert implementationIsBridge(code);
     BasicBlock entry = code.entryBlock();
-    InstructionListIterator iterator = entry.listIterator();
+    InstructionIterator iterator = entry.iterator();
     // Create a mapping from argument values to their index, while scanning over the arguments.
     Reference2IntMap<Value> argumentIndices = new Reference2IntArrayMap<>();
     while (iterator.peekNext().isArgument()) {
@@ -2720,7 +2720,7 @@
     if (code.blocks.size() > 1) {
       return false;
     }
-    InstructionListIterator iterator = code.entryBlock().listIterator();
+    InstructionIterator iterator = code.entryBlock().iterator();
     // Move forward to the first instruction after the definition of the arguments.
     while (iterator.hasNext() && iterator.peekNext().isArgument()) {
       iterator.next();
@@ -2913,7 +2913,7 @@
 
   private void insertRangeInvokeMoves() {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
+      InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isInvoke()) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index aae72b4..a5506d9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -165,7 +165,7 @@
    */
   public int scheduleAndInsertMoves(int tempRegister) {
     for (BasicBlock block : code.blocks) {
-      InstructionListIterator insertAt = block.listIterator();
+      InstructionListIterator insertAt = block.listIterator(code);
       if (block == code.entryBlock()) {
         // Move insertAt iterator to the first non-argument, such that moves for the arguments will
         // be inserted after the last argument.
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index c007b7b..725c68c 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -101,7 +101,7 @@
       if (blocks != null && !blocks.contains(block)) {
         continue;
       }
-      InstructionListIterator iterator = block.listIterator().recordChangesToMetadata(code);
+      InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
         // v_n <- "x.y.z" // in.definition
@@ -168,10 +168,10 @@
         block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
     if (blockWithFieldInstruction != block) {
       // If we split, add const-string at the end of the currently visiting block.
-      iterator = block.listIterator(block.getInstructions().size() - 1);
+      iterator = block.listIterator(code, block.getInstructions().size() - 1);
       iterator.add(decoupled);
       // Restore the cursor and block.
-      iterator = blockWithFieldInstruction.listIterator().recordChangesToMetadata(code);
+      iterator = blockWithFieldInstruction.listIterator(code);
       assert iterator.peekNext() == fieldPut;
       iterator.next();
     } else {
@@ -239,7 +239,7 @@
           iterator.replaceCurrentInstruction(decoupled);
           iterator.nextUntil(instruction -> instruction == invoke);
         } else {
-          in.definition.replace(decoupled, code.metadata());
+          in.definition.replace(decoupled, code);
         }
       } else {
         decoupled.setPosition(invoke.getPosition());
@@ -255,12 +255,11 @@
             block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
         if (blockWithInvoke != block) {
           // If we split, add const-string at the end of the currently visiting block.
-          iterator =
-              block.listIterator(block.getInstructions().size()).recordChangesToMetadata(code);
+          iterator = block.listIterator(code, block.getInstructions().size());
           iterator.previous();
           iterator.add(decoupled);
           // Restore the cursor and block.
-          iterator = blockWithInvoke.listIterator().recordChangesToMetadata(code);
+          iterator = blockWithInvoke.listIterator(code);
           assert iterator.peekNext() == invoke;
           iterator.next();
         } else {
@@ -304,12 +303,11 @@
             block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
         if (blockWithInvoke != block) {
           // If we split, add const-string at the end of the currently visiting block.
-          iterator =
-              block.listIterator(block.getInstructions().size()).recordChangesToMetadata(code);
+          iterator = block.listIterator(code, block.getInstructions().size());
           iterator.previous();
           iterator.add(decoupled);
           // Restore the cursor and block.
-          iterator = blockWithInvoke.listIterator().recordChangesToMetadata(code);
+          iterator = blockWithInvoke.listIterator(code);
           assert iterator.peekNext() == invoke;
           iterator.next();
         } else {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 4b072806..8cfd094 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.code.ConstantValueUtils;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -401,7 +401,7 @@
     // put into the array. Conservatively bail out if the content of the array cannot be statically
     // computed.
     BasicBlock block = newArray.getBlock();
-    InstructionListIterator iterator = block.listIterator();
+    InstructionIterator iterator = block.iterator();
     iterator.nextUntil(i -> i == newArray);
     do {
       while (iterator.hasNext()) {
@@ -453,7 +453,7 @@
       if (block.getPredecessors().size() != 1) {
         return null;
       }
-      iterator = block.listIterator();
+      iterator = block.iterator();
     } while (iterator != null);
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index e830d62..b7dcd14 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -54,6 +54,7 @@
 import com.android.tools.r8.ir.code.ConstantValueUtils;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
@@ -85,7 +86,6 @@
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -2102,7 +2102,7 @@
     DexType originHolder = method.method.holder;
     Origin origin = appInfo.originFor(originHolder);
     IRCode code = method.buildIR(appView, origin);
-    Iterator<Instruction> iterator = code.instructionIterator();
+    InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       handleReflectiveBehavior(method, instruction);
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index abffa51..b6cbe41 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import java.util.Iterator;
 import java.util.ListIterator;
 import java.util.function.Predicate;
@@ -35,6 +39,16 @@
     }
   }
 
+  /** @deprecated Use {@link #removeIf(InstructionListIterator, Predicate)} instead. */
+  @Deprecated
+  public static void removeIf(InstructionIterator iterator, Predicate<Instruction> predicate) {
+    throw new Unimplemented();
+  }
+
+  public static void removeIf(InstructionListIterator iterator, Predicate<Instruction> predicate) {
+    removeIf((Iterator<Instruction>) iterator, predicate);
+  }
+
   public static <T> boolean allRemainingMatch(ListIterator<T> iterator, Predicate<T> predicate) {
     return !anyRemainingMatch(iterator, remaining -> !predicate.test(remaining));
   }
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index f22aa2e..cf8f3f4 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -17,11 +17,11 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 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.util.List;
-import java.util.ListIterator;
 import org.junit.Test;
 
 /**
@@ -91,17 +91,17 @@
     BasicBlock entryBlock = code.entryBlock();
     BasicBlock tryBlock = code.blocks.get(1);
     assertTrue(tryBlock.hasCatchHandlers());
-    ListIterator<Instruction> it = entryBlock.getInstructions().listIterator();
+    InstructionListIterator it = entryBlock.listIterator(code);
     Instruction constNumber = it.next();
     while (!constNumber.isConstNumber()) {
       constNumber = it.next();
     }
-    it.remove();
+    it.removeInstructionIgnoreOutValue();
     Instruction add = it.next();
     while (!add.isAdd()) {
       add = it.next();
     }
-    it.remove();
+    it.removeInstructionIgnoreOutValue();
     constNumber.setBlock(tryBlock);
     add.setBlock(tryBlock);
     tryBlock.getInstructions().add(0, add);
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index 989550b..9394934 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -61,7 +61,7 @@
     MethodSubject methodSubject = getMethodSubject(application, signature);
     IRCode code = methodSubject.buildIR();
     ListIterator<BasicBlock> blocks = code.listIterator();
-    InstructionListIterator iter = blocks.next().listIterator();
+    InstructionListIterator iter = blocks.next().listIterator(code);
     iter.nextUntil(i -> !i.isArgument());
     iter.previous();
     iter.split(code, 1, blocks);
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 94ff10e..e20e882 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -38,7 +38,7 @@
 
 public class InlineTest extends IrInjectionTestBase {
 
-  public TestApplication buildTestApplication(
+  private TestApplication buildTestApplication(
       DexApplication application,
       InternalOptions options,
       MethodSubject method,
@@ -134,7 +134,7 @@
 
     // Run code inlining a.
     test = codeForMethodReplaceTest(a, b);
-    iterator = test.code.entryBlock().listIterator();
+    iterator = test.code.entryBlock().listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(0));
@@ -143,7 +143,7 @@
 
     // Run code inlining b (where a is actually called).
     test = codeForMethodReplaceTest(a, b);
-    iterator = test.code.entryBlock().listIterator();
+    iterator = test.code.entryBlock().listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(1));
@@ -213,7 +213,7 @@
 
     // Run code inlining a.
     test = codeForMethodReplaceReturnVoidTest(1, 2);
-    iterator = test.code.entryBlock().listIterator();
+    iterator = test.code.entryBlock().listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(0));
@@ -305,7 +305,7 @@
     Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     while (blocksIterator.hasNext()) {
       BasicBlock block = blocksIterator.next();
-      iterator = block.listIterator();
+      iterator = block.listIterator(test.code);
       Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
       if (invoke != null) {
         iterator.previous();
@@ -323,7 +323,7 @@
     inlinee = test.additionalCode.listIterator(3);  // IR code for b's
     while (blocksIterator.hasNext()) {
       BasicBlock block = blocksIterator.next();
-      iterator = block.listIterator();
+      iterator = block.listIterator(test.code);
       Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
       if (invoke != null) {
         iterator.previous();
@@ -427,7 +427,7 @@
 
     // Run code inlining a.
     test = codeForMethodReplaceTestWithCatchHandler(a, b, twoGuards);
-    iterator = test.code.blocks.get(1).listIterator();
+    iterator = test.code.blocks.get(1).listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(0));
@@ -436,7 +436,7 @@
 
     // Run code inlining b (where a is actually called).
     test = codeForMethodReplaceTestWithCatchHandler(a, b, twoGuards);
-    iterator = test.code.blocks.get(1).listIterator();
+    iterator = test.code.blocks.get(1).listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(1));
@@ -540,7 +540,7 @@
 
     // Run code inlining a.
     test = codeForInlineCanThrow(a, b, twoGuards);
-    iterator = test.code.entryBlock().listIterator();
+    iterator = test.code.entryBlock().listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(0));
@@ -549,7 +549,7 @@
 
     // Run code inlining b (where a is actually called).
     test = codeForInlineCanThrow(a, b, twoGuards);
-    iterator = test.code.entryBlock().listIterator();
+    iterator = test.code.entryBlock().listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(1));
@@ -651,7 +651,7 @@
 
     // Run code inlining a.
     test = codeForInlineAlwaysThrows(twoGuards);
-    iterator = test.code.entryBlock().listIterator();
+    iterator = test.code.entryBlock().listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(0));
@@ -661,7 +661,7 @@
 
     // Run code inlining b (where a is actually called).
     test = codeForInlineAlwaysThrows(twoGuards);
-    iterator = test.code.entryBlock().listIterator();
+    iterator = test.code.entryBlock().listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(1));
@@ -780,7 +780,7 @@
         if (blocksToRemove.contains(block)) {
           continue;
         }
-        iterator = block.listIterator();
+        iterator = block.listIterator(test.code);
         Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
         if (invoke != null) {
           iterator.previous();
@@ -804,7 +804,7 @@
         if (blocksToRemove.contains(block)) {
           continue;
         }
-        iterator = block.listIterator();
+        iterator = block.listIterator(test.code);
         Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
         if (invoke != null) {
           iterator.previous();
@@ -936,7 +936,7 @@
         if (blocksToRemove.contains(block)) {
           continue;
         }
-        iterator = block.listIterator();
+        iterator = block.listIterator(test.code);
         Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
         if (invoke != null) {
           iterator.previous();
@@ -960,7 +960,7 @@
         if (blocksToRemove.contains(block)) {
           continue;
         }
-        iterator = block.listIterator();
+        iterator = block.listIterator(test.code);
         Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
         if (invoke != null) {
           iterator.previous();
@@ -1169,7 +1169,7 @@
     // Run code inlining a.
     test = codeForInlineWithHandlersCanThrow(
         a, b, c, twoGuards, callerHasCatchAll, inlineeHasCatchAll);
-    iterator = test.code.blocks.get(1).listIterator();
+    iterator = test.code.blocks.get(1).listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(0));
@@ -1179,7 +1179,7 @@
     // Run code inlining b (where a is actually called).
     test = codeForInlineWithHandlersCanThrow(
         a, b, c, twoGuards, callerHasCatchAll, inlineeHasCatchAll);
-    iterator = test.code.blocks.get(1).listIterator();
+    iterator = test.code.blocks.get(1).listIterator(test.code);
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appView, test.code, test.additionalCode.get(1));
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
index 5ab4a24..10a30dc 100644
--- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
@@ -50,7 +49,7 @@
     MethodSubject methodSubject = getMethodSubject(application, signature);
     IRCode code = methodSubject.buildIR();
     ListIterator<BasicBlock> blocks = code.listIterator();
-    InstructionListIterator iter = blocks.next().listIterator();
+    InstructionListIterator iter = blocks.next().listIterator(code);
     iter.nextUntil(i -> !i.isArgument());
     iter.previous();
     iter.split(code, 1, blocks);
@@ -62,7 +61,7 @@
     IRCode code = simpleCode();
 
     ListIterator<BasicBlock> blocks = code.listIterator();
-    ListIterator<Instruction> instructions = blocks.next().listIterator();
+    InstructionListIterator instructions = blocks.next().listIterator(code);
     thrown.expect(IllegalStateException.class);
     instructions.remove();
   }
@@ -73,7 +72,7 @@
 
     ListIterator<BasicBlock> blocks = code.listIterator();
     blocks.next();
-    ListIterator<Instruction> instructions = blocks.next().listIterator();
+    InstructionListIterator instructions = blocks.next().listIterator(code);
     instructions.next();
     instructions.remove();
     thrown.expect(IllegalStateException.class);
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 77ed1bf..fc61691 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -27,7 +27,6 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.concurrent.ExecutionException;
 import org.antlr.runtime.RecognitionException;
 
@@ -98,7 +97,7 @@
 
     public int countArgumentInstructions() {
       int count = 0;
-      ListIterator<Instruction> iterator = code.entryBlock().listIterator();
+      InstructionIterator iterator = code.entryBlock().iterator();
       while (iterator.next().isArgument()) {
         count++;
       }
@@ -106,7 +105,7 @@
     }
 
     public InstructionListIterator listIteratorAt(BasicBlock block, int index) {
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator(code);
       for (int i = 0; i < index; i++) {
         iterator.next();
       }
diff --git a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
index c68810f..88785c4 100644
--- a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.LinearFlowInstructionIterator;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
@@ -22,7 +22,7 @@
 
 public class LinearFlowIteratorTest extends TestBase {
 
-  IRCode branchingCode() throws Exception {
+  private IRCode branchingCode() throws Exception {
 
     JasminBuilder jasminBuilder = new JasminBuilder();
 
@@ -58,14 +58,14 @@
     IRCode code = methodSubject.buildIR();
     ListIterator<BasicBlock> blocks = code.listIterator();
     blocks.next();
-    InstructionListIterator iter = blocks.next().listIterator();
+    InstructionListIterator iter = blocks.next().listIterator(code);
     iter.nextUntil(i -> !i.isConstNumber());
     iter.previous();
     iter.split(code, blocks);
     return code;
   }
 
-  IRCode simpleCode() throws Exception {
+  private IRCode simpleCode() throws Exception {
 
     JasminBuilder jasminBuilder = new JasminBuilder();
 
@@ -92,7 +92,7 @@
     MethodSubject method = getMethodSubject(app, "foo", "void", "bar", ImmutableList.of("int"));
     IRCode code = method.buildIR();
     ListIterator<BasicBlock> blocks = code.listIterator();
-    InstructionListIterator iter = blocks.next().listIterator();
+    InstructionListIterator iter = blocks.next().listIterator(code);
     iter.nextUntil(i -> !i.isArgument());
     iter.split(code, 0, blocks);
     return code;
@@ -101,35 +101,33 @@
   @Test
   public void hasNextWillCheckNextBlock() throws Exception {
     IRCode code = simpleCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
-    Instruction current = it.next();
-    current = it.next();
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.entryBlock());
+    it.next();
+    it.next();
     assert it.hasNext();
   }
 
   @Test
   public void nextWillContinueThroughGotoBlocks() throws Exception {
     IRCode code = simpleCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
-    Instruction current;
-    current = it.next(); // Argument
-    current = it.next(); // ConstNumber 0/NULL
-    current = it.next(); // ArrayGet
-    current = it.next(); // Return;
-    assert current.isReturn();
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.entryBlock());
+    it.next(); // Argument
+    it.next(); // ConstNumber 0/NULL
+    it.next(); // ArrayGet
+    assert it.next().isReturn(); // Return
   }
 
   @Test
   public void hasPreviousWillCheckPreviousBlock() throws Exception {
     IRCode code = simpleCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(2));
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(2));
     assert it.hasPrevious();
   }
 
   @Test
   public void hasPreviousWillJumpOverGotos() throws Exception {
     IRCode code = simpleCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(2));
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(2));
     assert it.previous().isConstNumber();
   }
 
@@ -137,7 +135,7 @@
   public void GoToFrontAndBackIsSameAmountOfInstructions() throws Exception {
     IRCode code = simpleCode();
     int moves = 0;
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.entryBlock());
     while (it.hasNext()) {
       it.next();
       moves++;
@@ -153,7 +151,7 @@
   @Test
   public void moveFromEmptyBlock() throws Exception {
     IRCode code = simpleCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(1));
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(1));
     Instruction current = it.previous();
     assertTrue(current.isConstNumber() && current.outValue().getTypeLattice().isReference());
     it.next();
@@ -164,23 +162,23 @@
   @Test
   public void doNotChangeToNextBlockWhenNotLinearFlow() throws Exception {
     IRCode code = branchingCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.entryBlock());
     it.nextUntil((i) -> !i.isArgument());
-    Instruction current = it.next();
+    it.next();
     assert !it.hasNext();
   }
 
   @Test
   public void doNotChangeToPreviousBlockWhenNotLinearFlow() throws Exception {
     IRCode code = branchingCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(4));
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(4));
     assert !it.hasPrevious();
   }
 
   @Test
   public void followLinearSubPathDown() throws Exception {
     IRCode code = branchingCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(1));
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(1));
     Instruction current = null;
     while (it.hasNext()) {
       current = it.next();
@@ -191,7 +189,7 @@
   @Test
   public void followLinearSubPathUp() throws Exception {
     IRCode code = branchingCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(2));
+    InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(2));
     Instruction current = null;
     while (it.hasPrevious()) {
       current = it.previous();
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 35e89c1..111e8f1 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -354,7 +354,7 @@
     List<BasicBlock> exitBlocks = new ArrayList<>(code.computeNormalExitBlocks());
     for (BasicBlock originalReturnBlock : exitBlocks) {
       InstructionListIterator iterator =
-          originalReturnBlock.listIterator(originalReturnBlock.getInstructions().size());
+          originalReturnBlock.listIterator(code, originalReturnBlock.getInstructions().size());
       Instruction ret = iterator.previous();
       assert ret.isReturn();
       BasicBlock newReturnBlock = iterator.split(code);
@@ -364,8 +364,8 @@
           new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
       Value newReturnValue =
           new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
-      Value oldReturnValue = newReturnBlock.listIterator().next().asReturn().returnValue();
-      newReturnBlock.listIterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
+      Value oldReturnValue = newReturnBlock.iterator().next().asReturn().returnValue();
+      newReturnBlock.iterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
       Instruction constInstruction = new ConstNumber(newConstValue, 10);
       Instruction addInstruction =
           new Add(NumericType.INT, newReturnValue, oldReturnValue, newConstValue);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index c6c0d73..5782025 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -82,10 +81,8 @@
   }
 
   private void verifyLastInvoke(IRCode code, boolean npeCaught) {
-    InstructionIterator it = code.instructionIterator();
     boolean metInvokeVirtual = false;
-    while (it.hasNext()) {
-      Instruction instruction = it.next();
+    for (Instruction instruction : code.instructions()) {
       if (instruction.isInvokeMethodWithReceiver()) {
         InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
         if (invoke.getInvokedMethod().name.toString().contains("hash")) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 803ca02..91fdee5 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -160,11 +159,9 @@
   private static void fillArrayData(AppView<?> appView, CodeInspector inspector) {
     MethodSubject test1Subject =
         inspector.clazz("Test").method(new MethodSignature("test1", "int[]", ImmutableList.of()));
-    IRCode irCode = test1Subject.buildIR();
+    IRCode code = test1Subject.buildIR();
     Value array = null;
-    InstructionIterator iterator = irCode.instructionIterator();
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
+    for (Instruction instruction : code.instructions()) {
       if (instruction instanceof NewArrayEmpty) {
         array = instruction.outValue();
         break;
@@ -172,26 +169,26 @@
     }
     assertNotNull(array);
     final Value finalArray = array;
-    forEachOutValue(irCode, (v, l) -> {
-      if (v == finalArray) {
-        assertTrue(l.isArrayType());
-        ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-        assertTrue(lattice.getArrayMemberTypeAsMemberType().isPrimitive());
-        assertEquals(1, lattice.getNesting());
-        assertFalse(lattice.isNullable());
-      }
-    });
+    forEachOutValue(
+        code,
+        (v, l) -> {
+          if (v == finalArray) {
+            assertTrue(l.isArrayType());
+            ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+            assertTrue(lattice.getArrayMemberTypeAsMemberType().isPrimitive());
+            assertEquals(1, lattice.getNesting());
+            assertFalse(lattice.isNullable());
+          }
+        });
   }
 
   // filled-new-array
   private static void filledNewArray(AppView<?> appView, CodeInspector inspector) {
     MethodSubject test4Subject =
         inspector.clazz("Test").method(new MethodSignature("test4", "int[]", ImmutableList.of()));
-    IRCode irCode = test4Subject.buildIR();
+    IRCode code = test4Subject.buildIR();
     Value array = null;
-    InstructionIterator iterator = irCode.instructionIterator();
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
+    for (Instruction instruction : code.instructions()) {
       if (instruction instanceof InvokeNewArray) {
         array = instruction.outValue();
         break;
@@ -199,15 +196,17 @@
     }
     assertNotNull(array);
     final Value finalArray = array;
-    forEachOutValue(irCode, (v, l) -> {
-      if (v == finalArray) {
-        assertTrue(l.isArrayType());
-        ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-        assertTrue(lattice.getArrayMemberTypeAsMemberType().isPrimitive());
-        assertEquals(1, lattice.getNesting());
-        assertFalse(lattice.isNullable());
-      }
-    });
+    forEachOutValue(
+        code,
+        (v, l) -> {
+          if (v == finalArray) {
+            assertTrue(l.isArrayType());
+            ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+            assertTrue(lattice.getArrayMemberTypeAsMemberType().isPrimitive());
+            assertEquals(1, lattice.getNesting());
+            assertFalse(lattice.isNullable());
+          }
+        });
   }
 
   // Make sure the analysis does not hang.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index bac6888..deec11f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -35,22 +35,22 @@
     AppView<?> appView = build(testClass);
     CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(testClass.getName()).method(signature);
-    IRCode irCode = fooSubject.buildIR();
-    checkCountOfNonNull(irCode, 0);
+    IRCode code = fooSubject.buildIR();
+    checkCountOfNonNull(code, 0);
 
     NonNullTracker nonNullTracker = new NonNullTracker(appView);
 
-    nonNullTracker.addNonNull(irCode);
-    assertTrue(irCode.isConsistentSSA());
-    checkCountOfNonNull(irCode, expectedNumberOfNonNull);
+    nonNullTracker.addNonNull(code);
+    assertTrue(code.isConsistentSSA());
+    checkCountOfNonNull(code, expectedNumberOfNonNull);
 
     if (testAugmentedIRCode != null) {
-      testAugmentedIRCode.accept(irCode);
+      testAugmentedIRCode.accept(code);
     }
 
-    new CodeRewriter(appView, null).removeAssumeInstructions(irCode);
-    assertTrue(irCode.isConsistentSSA());
-    checkCountOfNonNull(irCode, 0);
+    new CodeRewriter(appView, null).removeAssumeInstructions(code);
+    assertTrue(code.isConsistentSSA());
+    checkCountOfNonNull(code, 0);
   }
 
   private static void checkCountOfNonNull(IRCode code, int expectedOccurrences) {
@@ -75,16 +75,16 @@
     assertEquals(expectedOccurrences, count);
   }
 
-  private void checkInvokeGetsNonNullReceiver(IRCode irCode) {
-    checkInvokeReceiver(irCode, true);
+  private void checkInvokeGetsNonNullReceiver(IRCode code) {
+    checkInvokeReceiver(code, true);
   }
 
-  private void checkInvokeGetsNullReceiver(IRCode irCode) {
-    checkInvokeReceiver(irCode, false);
+  private void checkInvokeGetsNullReceiver(IRCode code) {
+    checkInvokeReceiver(code, false);
   }
 
-  private void checkInvokeReceiver(IRCode irCode, boolean isNotNull) {
-    InstructionIterator it = irCode.instructionIterator();
+  private void checkInvokeReceiver(IRCode code, boolean isNotNull) {
+    InstructionIterator it = code.instructionIterator();
     boolean metInvokeWithReceiver = false;
     while (it.hasNext()) {
       Instruction instruction = it.nextUntil(Instruction::isInvokeMethodWithReceiver);
@@ -146,10 +146,10 @@
         NonNullAfterFieldAccess.class,
         signature,
         1,
-        ircode -> {
+        code -> {
           // There are two InstancePut instructions of interest.
           int count = 0;
-          InstructionIterator it = ircode.instructionIterator();
+          InstructionIterator it = code.instructionIterator();
           while (it.hasNext()) {
             Instruction instruction = it.nextUntil(Instruction::isInstancePut);
             if (instruction == null) {