Gather metadata about IR and simplify maintenance

Bug: 122257895
Change-Id: I6e45613f5ae887e93fcd9550490f1760ad8fe18f
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 dbd95ae..118ea7a 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
@@ -120,7 +120,8 @@
       IRCode code, Value infoValue, ProtoMessageInfo protoMessageInfo) {
     infoValue.definition.replace(
         new ConstString(
-            code.createValue(stringType), encoder.encodeInfo(protoMessageInfo), throwingInfo));
+            code.createValue(stringType), encoder.encodeInfo(protoMessageInfo), throwingInfo),
+        code.metadata());
   }
 
   private void rewriteObjectsArgumentToNewMessageInfo(
@@ -130,7 +131,8 @@
       ProtoMessageInfo protoMessageInfo) {
     // Position iterator immediately before the call to newMessageInfo().
     BasicBlock block = newMessageInfoInvoke.getBlock();
-    InstructionListIterator instructionIterator = block.listIterator(newMessageInfoInvoke);
+    InstructionListIterator instructionIterator =
+        block.listIterator(newMessageInfoInvoke).recordChangesToMetadata(code);
     Instruction previous = instructionIterator.previous();
     instructionIterator.setInsertionPosition(newMessageInfoInvoke.getPosition());
     assert previous == newMessageInfoInvoke;
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
index 698592d..5ebbc17 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -26,13 +26,15 @@
 import java.util.ListIterator;
 import java.util.Set;
 
-public class BasicBlockInstructionIterator implements InstructionIterator, InstructionListIterator {
+public class BasicBlockInstructionIterator implements InstructionListIterator {
 
   protected final BasicBlock block;
   protected final ListIterator<Instruction> listIterator;
   protected Instruction current;
   protected Position position = null;
 
+  private UpdatableIRMetadata metadata;
+
   BasicBlockInstructionIterator(BasicBlock block) {
     this.block = block;
     this.listIterator = block.getInstructions().listIterator();
@@ -49,6 +51,16 @@
   }
 
   @Override
+  public BasicBlockInstructionIterator recordChangesToMetadata(IRMetadata metadata) {
+    if (metadata.isUpdatableIRMetadata()) {
+      this.metadata = metadata.asUpdatableIRMetadata();
+    } else {
+      assert metadata.isUnknownIRMetadata();
+    }
+    return this;
+  }
+
+  @Override
   public boolean hasNext() {
     return listIterator.hasNext();
   }
@@ -101,6 +113,9 @@
       instruction.setPosition(position);
     }
     listIterator.add(instruction);
+    if (metadata != null) {
+      metadata.record(instruction);
+    }
   }
 
   /**
@@ -182,6 +197,9 @@
     listIterator.remove();
     listIterator.add(newInstruction);
     current.clearBlock();
+    if (metadata != null) {
+      metadata.record(newInstruction);
+    }
   }
 
   @Override
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 e6b352e..a07e11b 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
@@ -105,12 +105,7 @@
   // If this is the case, which holds for javac code, then we want to ensure that it remains so.
   private boolean allThrowingInstructionsHavePositions;
 
-  // TODO(b/122257895): Update OptimizationInfo to capture instruction kinds of interest.
-  public final boolean mayHaveDebugPositions;
-  public boolean mayHaveConstString;
-  public boolean mayHaveStringSwitch;
-  public final boolean mayHaveMonitorInstruction;
-
+  private final IRMetadata metadata;
   private final InternalOptions options;
 
   public final Origin origin;
@@ -120,27 +115,27 @@
       DexEncodedMethod method,
       LinkedList<BasicBlock> blocks,
       ValueNumberGenerator valueNumberGenerator,
-      boolean mayHaveDebugPositions,
-      boolean mayHaveMonitorInstruction,
-      boolean mayHaveConstString,
+      IRMetadata metadata,
       Origin origin) {
+    assert metadata != null;
     assert options != null;
     this.options = options;
     this.method = method;
     this.blocks = blocks;
     this.valueNumberGenerator = valueNumberGenerator;
-    this.mayHaveDebugPositions = mayHaveDebugPositions;
-    this.mayHaveMonitorInstruction = mayHaveMonitorInstruction;
-    this.mayHaveConstString = mayHaveConstString;
+    this.metadata = metadata;
     this.origin = origin;
     // TODO(zerny): Remove or update this property now that all instructions have positions.
     allThrowingInstructionsHavePositions = computeAllThrowingInstructionsHavePositions();
   }
 
+  public IRMetadata metadata() {
+    return metadata;
+  }
+
   public void mergeMetadataFromInlinee(IRCode inlinee) {
-    assert !inlinee.mayHaveMonitorInstruction;
-    this.mayHaveConstString |= inlinee.mayHaveConstString;
-    this.mayHaveStringSwitch |= inlinee.mayHaveStringSwitch;
+    assert !inlinee.metadata.mayHaveMonitorInstruction();
+    this.metadata.merge(inlinee.metadata);
   }
 
   public BasicBlock entryBlock() {
@@ -719,13 +714,20 @@
   private boolean consistentMetadata() {
     for (Instruction instruction : instructions()) {
       if (instruction.isConstString()) {
-        assert mayHaveConstString : "IR metadata should indicate that code has a const-string";
+        assert metadata.mayHaveConstString()
+            : "IR metadata should indicate that code has a const-string";
       } else if (instruction.isDebugPosition()) {
-        assert mayHaveDebugPositions : "IR metadata should indicate that code has a debug position";
+        assert metadata.mayHaveDebugPosition()
+            : "IR metadata should indicate that code has a debug position";
+      } else if (instruction.isDexItemBasedConstString()) {
+        assert metadata.mayHaveDexItemBasedConstString()
+            : "IR metadata should indicate that code has a dex-item-based-const-string";
       } else if (instruction.isMonitor()) {
-        assert mayHaveMonitorInstruction : "IR metadata should indicate that code has a monitor";
+        assert metadata.mayHaveMonitorInstruction()
+            : "IR metadata should indicate that code has a monitor instruction";
       } else if (instruction.isStringSwitch()) {
-        assert mayHaveStringSwitch : "IR metadata should indicate that code has a string-switch";
+        assert metadata.mayHaveStringSwitch()
+            : "IR metadata should indicate that code has a string-switch";
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
index 03dd1fa..d2bd712 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
@@ -12,12 +12,24 @@
   private final ListIterator<BasicBlock> blockIterator;
   private InstructionListIterator instructionIterator;
 
+  private IRMetadata metadata;
+
   public IRCodeInstructionsIterator(IRCode code) {
     blockIterator = code.listIterator();
     instructionIterator = blockIterator.next().listIterator();
   }
 
   @Override
+  public IRCodeInstructionsIterator recordChangesToMetadata(IRMetadata metadata) {
+    if (metadata.isUpdatableIRMetadata()) {
+      this.metadata = metadata.asUpdatableIRMetadata();
+    } else {
+      assert metadata.isUnknownIRMetadata();
+    }
+    return this;
+  }
+
+  @Override
   public boolean hasNext() {
     return instructionIterator.hasNext() || blockIterator.hasNext();
   }
@@ -67,6 +79,9 @@
   @Override
   public void add(Instruction instruction) {
     instructionIterator.add(instruction);
+    if (metadata != null) {
+      metadata.record(instruction);
+    }
   }
 
   @Override
@@ -77,11 +92,17 @@
   @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/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
new file mode 100644
index 0000000..a79d45d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -0,0 +1,38 @@
+// 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;
+
+public abstract class IRMetadata {
+
+  public static IRMetadata unknown() {
+    return UnknownIRMetadata.getInstance();
+  }
+
+  public boolean isUpdatableIRMetadata() {
+    return false;
+  }
+
+  public UpdatableIRMetadata asUpdatableIRMetadata() {
+    return null;
+  }
+
+  public boolean isUnknownIRMetadata() {
+    return false;
+  }
+
+  public abstract void record(Instruction instruction);
+
+  public abstract void merge(IRMetadata metadata);
+
+  public abstract boolean mayHaveConstString();
+
+  public abstract boolean mayHaveDebugPosition();
+
+  public abstract boolean mayHaveDexItemBasedConstString();
+
+  public abstract boolean mayHaveMonitorInstruction();
+
+  public abstract boolean mayHaveStringSwitch();
+}
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 8e11152..95e8ca6 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
@@ -272,8 +272,11 @@
     getBlock().listIterator(this).removeOrReplaceByDebugLocalRead();
   }
 
-  public void replace(Instruction newInstruction) {
-    getBlock().listIterator(this).replaceCurrentInstruction(newInstruction);
+  public void replace(Instruction newInstruction, IRMetadata metadata) {
+    getBlock()
+        .listIterator(this)
+        .recordChangesToMetadata(metadata)
+        .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 72aa3ee..d957147 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,10 +4,21 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.errors.Unreachable;
 import java.util.ListIterator;
 
 public interface InstructionIterator
     extends ListIterator<Instruction>, NextUntilIterator<Instruction> {
+
+  default InstructionIterator recordChangesToMetadata(IRCode code) {
+    return recordChangesToMetadata(code.metadata());
+  }
+
+  default InstructionIterator recordChangesToMetadata(IRMetadata metadata) {
+    throw new Unreachable(
+        "Method recordChangesToMetadata(IRMetadata) not implemented for "
+            + getClass().getTypeName());
+  }
   /**
    * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
    * {@link #next} with the passed in <code>newInstruction</code>.
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 59a77a1..773bc13 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,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -45,6 +46,18 @@
     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());
+  }
+
   default void setInsertionPosition(Position position) {
     // Intentionally empty.
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/UnknownIRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/UnknownIRMetadata.java
new file mode 100644
index 0000000..8730fb5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/UnknownIRMetadata.java
@@ -0,0 +1,56 @@
+// 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;
+
+public class UnknownIRMetadata extends IRMetadata {
+
+  private static UnknownIRMetadata INSTANCE = new UnknownIRMetadata();
+
+  private UnknownIRMetadata() {}
+
+  static UnknownIRMetadata getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isUnknownIRMetadata() {
+    return true;
+  }
+
+  @Override
+  public void record(Instruction instruction) {
+    // Nothing to do.
+  }
+
+  @Override
+  public void merge(IRMetadata metadata) {
+    // Nothing to do.
+  }
+
+  @Override
+  public boolean mayHaveConstString() {
+    return true;
+  }
+
+  @Override
+  public boolean mayHaveDebugPosition() {
+    return true;
+  }
+
+  @Override
+  public boolean mayHaveDexItemBasedConstString() {
+    return true;
+  }
+
+  @Override
+  public boolean mayHaveMonitorInstruction() {
+    return true;
+  }
+
+  @Override
+  public boolean mayHaveStringSwitch() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/UpdatableIRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/UpdatableIRMetadata.java
new file mode 100644
index 0000000..bcceb5b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/UpdatableIRMetadata.java
@@ -0,0 +1,68 @@
+// 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;
+
+public class UpdatableIRMetadata extends IRMetadata {
+
+  // TODO(b/122257895): change to bit vector based representation.
+  private boolean mayHaveConstString;
+  private boolean mayHaveDebugPosition;
+  private boolean mayHaveDexItemBasedConstString;
+  private boolean mayHaveMonitorInstruction;
+  private boolean mayHaveStringSwitch;
+
+  @Override
+  public boolean isUpdatableIRMetadata() {
+    return true;
+  }
+
+  @Override
+  public UpdatableIRMetadata asUpdatableIRMetadata() {
+    return this;
+  }
+
+  @Override
+  public void record(Instruction instruction) {
+    mayHaveConstString |= instruction.isConstString();
+    mayHaveDebugPosition |= instruction.isDebugPosition();
+    mayHaveDexItemBasedConstString |= instruction.isDexItemBasedConstString();
+    mayHaveMonitorInstruction |= instruction.isMonitor();
+    mayHaveStringSwitch |= instruction.isStringSwitch();
+  }
+
+  @Override
+  public void merge(IRMetadata metadata) {
+    this.mayHaveConstString |= metadata.mayHaveConstString();
+    this.mayHaveDebugPosition |= metadata.mayHaveDebugPosition();
+    this.mayHaveDexItemBasedConstString |= metadata.mayHaveDexItemBasedConstString();
+    this.mayHaveMonitorInstruction |= metadata.mayHaveMonitorInstruction();
+    this.mayHaveStringSwitch |= metadata.mayHaveStringSwitch();
+  }
+
+  @Override
+  public boolean mayHaveConstString() {
+    return mayHaveConstString;
+  }
+
+  @Override
+  public boolean mayHaveDebugPosition() {
+    return mayHaveDebugPosition;
+  }
+
+  @Override
+  public boolean mayHaveDexItemBasedConstString() {
+    return mayHaveDexItemBasedConstString;
+  }
+
+  @Override
+  public boolean mayHaveMonitorInstruction() {
+    return mayHaveMonitorInstruction;
+  }
+
+  @Override
+  public boolean mayHaveStringSwitch() {
+    return mayHaveStringSwitch;
+  }
+}
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 77b3058..ba9a714 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
@@ -334,7 +334,7 @@
   // After this pass all remaining debug positions mark places where we must ensure a materializing
   // instruction, eg, for two successive lines without intermediate instructions.
   public static void removeRedundantDebugPositions(IRCode code) {
-    if (!code.mayHaveDebugPositions) {
+    if (!code.metadata().mayHaveDebugPosition()) {
       return;
     }
     // Current position known to have a materializing instruction associated with it.
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 ef6f3eb..ace2187 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
@@ -92,6 +92,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Throw;
+import com.android.tools.r8.ir.code.UpdatableIRMetadata;
 import com.android.tools.r8.ir.code.Ushr;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -414,10 +415,9 @@
   private boolean hasImpreciseValues = false;
 
   // Flag indicating if a const string is ever loaded.
-  private boolean hasConstString = false;
 
   // Flag indicating if the code has a monitor instruction.
-  private boolean hasMonitorInstruction = false;
+  private final UpdatableIRMetadata metadata = new UpdatableIRMetadata();
 
   public IRBuilder(DexEncodedMethod method, AppView<?> appView, SourceCode source, Origin origin) {
     this(method, appView, source, origin, new ValueNumberGenerator());
@@ -553,7 +553,7 @@
     assert verifyFilledPredecessors();
 
     // Insert debug positions so all position changes are marked by an explicit instruction.
-    boolean hasDebugPositions = insertDebugPositions();
+    insertDebugPositions();
 
     // Insert definitions for all uninitialized local values.
     if (uninitializedDebugLocalValues != null) {
@@ -584,15 +584,7 @@
 
     // Package up the IR code.
     IRCode ir =
-        new IRCode(
-            appView.options(),
-            method,
-            blocks,
-            valueNumberGenerator,
-            hasDebugPositions,
-            hasMonitorInstruction,
-            hasConstString,
-            origin);
+        new IRCode(appView.options(), method, blocks, valueNumberGenerator, metadata, origin);
 
     // Verify critical edges are split so we have a place to insert phi moves if necessary.
     assert ir.verifySplitCriticalEdges();
@@ -636,10 +628,9 @@
     impreciseInstructions.add(instruction);
   }
 
-  private boolean insertDebugPositions() {
-    boolean hasDebugPositions = false;
+  private void insertDebugPositions() {
     if (!isDebugMode()) {
-      return hasDebugPositions;
+      return;
     }
     for (BasicBlock block : blocks) {
       InstructionListIterator it = block.listIterator();
@@ -650,13 +641,12 @@
         if (instruction.isMoveException()) {
           assert current == null;
           current = position;
-          hasDebugPositions = hasDebugPositions || position.isSome();
         } else if (instruction.isDebugPosition()) {
           if (position.equals(current)) {
             it.removeOrReplaceByDebugLocalRead();
           } else {
             current = position;
-            hasDebugPositions = true;
+            metadata.record(instruction);
           }
         } else if (position.isSome() && !position.synthetic && !position.equals(current)) {
           DebugPosition positionChange = new DebugPosition();
@@ -665,11 +655,10 @@
           it.add(positionChange);
           it.next();
           current = position;
-          hasDebugPositions = true;
+          metadata.record(positionChange);
         }
       }
     }
-    return hasDebugPositions;
   }
 
   private boolean verifyFilledPredecessors() {
@@ -833,8 +822,8 @@
    */
   public void add(Instruction ir) {
     assert !ir.isJumpInstruction();
-    hasConstString |= ir.isConstString() || ir.isDexItemBasedConstString();
     addInstruction(ir);
+    metadata.record(ir);
   }
 
   private RemovedArgumentInfo getRemovedArgumentInfo() {
@@ -1168,7 +1157,6 @@
     Value in = readRegister(monitor, ValueTypeConstraint.OBJECT);
     Monitor monitorEnter = new Monitor(type, in);
     add(monitorEnter);
-    hasMonitorInstruction = true;
     return monitorEnter;
   }
 
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 e208ec3..f1396b8 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
@@ -108,12 +108,11 @@
     if (rewritingCandidates != null) {
       boolean changed = false;
       for (BasicBlock block : rewritingCandidates) {
-        if (convertRewritingCandidateToStringSwitchInstruction(block, dexItemFactory)) {
+        if (convertRewritingCandidateToStringSwitchInstruction(code, block, dexItemFactory)) {
           changed = true;
         }
       }
       if (changed) {
-        code.mayHaveStringSwitch = true;
         code.removeAllTrivialPhis();
         code.removeUnreachableBlocks();
       }
@@ -172,10 +171,10 @@
   }
 
   private static boolean convertRewritingCandidateToStringSwitchInstruction(
-      BasicBlock block, DexItemFactory dexItemFactory) {
+      IRCode code, BasicBlock block, DexItemFactory dexItemFactory) {
     StringSwitchBuilderInfo info = StringSwitchBuilderInfo.builder(dexItemFactory).build(block);
     if (info != null) {
-      info.createAndInsertStringSwitch();
+      info.createAndInsertStringSwitch(code);
       return true;
     }
     return false;
@@ -269,7 +268,7 @@
       return new Builder(dexItemFactory);
     }
 
-    void createAndInsertStringSwitch() {
+    void createAndInsertStringSwitch(IRCode code) {
       // Remove outgoing control flow edges from `insertionBlock`.
       for (BasicBlock successor : insertionBlock.getNormalSuccessors()) {
         successor.removePredecessor(insertionBlock, null);
@@ -288,9 +287,10 @@
         i++;
       }
       insertionBlock.link(fallthroughBlock);
-      insertionBlock
-          .exit()
-          .replace(new StringSwitch(value, keys, targetBlockIndices, i + numberOfCatchHandlers));
+      JumpInstruction exit = insertionBlock.exit();
+      exit.replace(
+          new StringSwitch(value, keys, targetBlockIndices, i + numberOfCatchHandlers),
+          code.metadata());
     }
   }
 
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 9efba14..4cf5299 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
@@ -47,7 +47,7 @@
   }
 
   void run(DexEncodedMethod method, IRCode code) {
-    if (!code.mayHaveStringSwitch) {
+    if (!code.metadata().mayHaveStringSwitch()) {
       assert Streams.stream(code.instructions()).noneMatch(Instruction::isStringSwitch);
       return;
     }
@@ -112,7 +112,7 @@
 
       if (previous == null) {
         // Replace the string-switch instruction by a goto instruction.
-        block.exit().replace(new Goto(newBlock));
+        block.exit().replace(new Goto(newBlock), code.metadata());
         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/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index f68f740..785799d 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();
+      InstructionListIterator instructions = block.listIterator().recordChangesToMetadata(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
         if (!instruction.isInvokeCustom()) {
@@ -447,7 +447,6 @@
                 value,
                 factory.createString(str),
                 ThrowingInfo.defaultForConstString(appView.options())));
-        code.mayHaveConstString = true;
         return value;
       }
     }
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 862c82d..4bfb703 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
@@ -1090,7 +1090,7 @@
                   targets,
                   switchInsn.getFallthroughBlockIndex());
           // Replace the switch itself.
-          exit.replace(newSwitch);
+          exit.replace(newSwitch, code.metadata());
           // 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;
@@ -3652,7 +3652,7 @@
   }
 
   public void rewriteConstantEnumMethodCalls(IRCode code) {
-    InstructionIterator iterator = code.instructionIterator();
+    InstructionIterator iterator = code.instructionIterator().recordChangesToMetadata(code);
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
 
@@ -3699,7 +3699,6 @@
       } else if (isNameInvoke) {
         iterator.replaceCurrentInstruction(
             new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
-        code.mayHaveConstString = true;
       } else {
         assert isToStringInvoke;
         DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
@@ -3713,7 +3712,6 @@
         }
         iterator.replaceCurrentInstruction(
             new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
-        code.mayHaveConstString = true;
       }
     }
 
@@ -4103,7 +4101,6 @@
     }
     // When we fall out of the loop the iterator is in the last eol block.
     iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, empty)));
-    code.mayHaveConstString = true;
   }
 
   public static void ensureDirectStringNewToInit(IRCode code, DexItemFactory dexItemFactory) {
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 0f6bddd..00b4540 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
@@ -120,7 +120,7 @@
         // 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.mayHaveMonitorInstruction) {
+            && code.metadata().mayHaveMonitorInstruction()) {
           continue;
         }
         // Constants with local info must not be canonicalized and must be filtered.
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 f8b0eae..2326b45 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,12 +233,11 @@
       }
       replacement.setPosition(current.getPosition());
       if (current.getBlock().hasCatchHandlers()) {
-        iterator.split(code, blocks).listIterator().add(replacement);
+        iterator.split(code, blocks).listIterator().recordChangesToMetadata(code).add(replacement);
       } else {
         iterator.add(replacement);
       }
     }
-    code.mayHaveConstString |= replacement.isConstString();
     return true;
   }
 
@@ -308,7 +307,6 @@
         replacement =
             createConstStringReplacement(
                 code, constant, current.outValue().getTypeLattice(), current.getLocalInfo());
-        code.mayHaveConstString = true;
       }
 
       affectedValues.addAll(current.outValue().affectedValues());
@@ -317,7 +315,7 @@
       replacement.setPosition(current.getPosition());
       current.moveDebugValues(replacement);
       if (current.getBlock().hasCatchHandlers()) {
-        iterator.split(code, blocks).listIterator().add(replacement);
+        iterator.split(code, blocks).listIterator().recordChangesToMetadata(code).add(replacement);
       } else {
         iterator.add(replacement);
       }
@@ -370,7 +368,6 @@
       if (replacement != null) {
         affectedValues.addAll(current.outValue().affectedValues());
         iterator.replaceCurrentInstruction(replacement);
-        code.mayHaveConstString |= replacement.isConstString();
         if (replacement.isDexItemBasedConstString()) {
           code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
         }
@@ -434,7 +431,6 @@
     if (replacement != null) {
       affectedValues.add(replacement.outValue());
       iterator.replaceCurrentInstruction(replacement);
-      code.mayHaveConstString |= replacement.isConstString();
       if (replacement.isDexItemBasedConstString()) {
         code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
       }
@@ -461,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().add(nonNull);
+      iterator.split(code, blocks).listIterator().recordChangesToMetadata(code).add(nonNull);
     } else {
       iterator.add(nonNull);
     }
@@ -479,7 +475,7 @@
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator().recordChangesToMetadata(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
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 cf4d14b..60c34a1 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
@@ -488,8 +488,9 @@
         }
       }
       List<Value> args = invoke.inValues();
-      invoke.replace(new InvokeStatic(
-          invoke.getInvokedMethod(), newValue, args.subList(1, args.size())));
+      invoke.replace(
+          new InvokeStatic(invoke.getInvokedMethod(), newValue, args.subList(1, args.size())),
+          code.metadata());
     }
   }
 
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 6a6a05e..3adb9d4 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
@@ -63,7 +63,7 @@
   // String String#substring(int)
   // String String#substring(int, int)
   public void computeTrivialOperationsOnConstString(IRCode code) {
-    if (!code.mayHaveConstString) {
+    if (!code.metadata().mayHaveConstString()) {
       return;
     }
     InstructionIterator it = code.instructionIterator();
@@ -207,7 +207,7 @@
       return;
     }
     boolean markUseIdentifierNameString = false;
-    InstructionIterator it = code.instructionIterator();
+    InstructionIterator it = code.instructionIterator().recordChangesToMetadata(code);
     while (it.hasNext()) {
       Instruction instr = it.next();
       if (!instr.isInvokeVirtual()) {
@@ -339,7 +339,6 @@
                 invoke.getLocalInfo());
         ConstString constString = new ConstString(stringValue, name, throwingInfo);
         it.replaceCurrentInstruction(constString);
-        code.mayHaveConstString = true;
       } else if (deferred != null) {
         it.replaceCurrentInstruction(deferred);
         markUseIdentifierNameString = true;
@@ -354,7 +353,7 @@
   // String#valueOf(String s) -> s
   // str.toString() -> str
   public void removeTrivialConversions(IRCode code) {
-    InstructionIterator it = code.instructionIterator();
+    InstructionIterator it = code.instructionIterator().recordChangesToMetadata(code);
     while (it.hasNext()) {
       Instruction instr = it.next();
       if (instr.isInvokeStatic()) {
@@ -377,7 +376,6 @@
           ConstString nullString =
               new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
           it.replaceCurrentInstruction(nullString);
-          code.mayHaveConstString = true;
         } else if (inType.nullability().isDefinitelyNotNull()
             && inType.isClassType()
             && inType.asClassTypeLatticeElement().getClassType().equals(factory.stringType)) {
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 4ce71a9..c007b7b 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -92,7 +92,7 @@
 
   public void decoupleIdentifierNameStringsInBlocks(
       DexEncodedMethod method, IRCode code, Set<BasicBlock> blocks) {
-    if (!code.mayHaveConstString) {
+    if (!code.metadata().mayHaveConstString()) {
       return;
     }
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -101,7 +101,7 @@
       if (blocks != null && !blocks.contains(block)) {
         continue;
       }
-      InstructionListIterator iterator = block.listIterator();
+      InstructionListIterator iterator = block.listIterator().recordChangesToMetadata(code);
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
         // v_n <- "x.y.z" // in.definition
@@ -171,7 +171,7 @@
       iterator = block.listIterator(block.getInstructions().size() - 1);
       iterator.add(decoupled);
       // Restore the cursor and block.
-      iterator = blockWithFieldInstruction.listIterator();
+      iterator = blockWithFieldInstruction.listIterator().recordChangesToMetadata(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);
+          in.definition.replace(decoupled, code.metadata());
         }
       } else {
         decoupled.setPosition(invoke.getPosition());
@@ -255,11 +255,12 @@
             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());
+          iterator =
+              block.listIterator(block.getInstructions().size()).recordChangesToMetadata(code);
           iterator.previous();
           iterator.add(decoupled);
           // Restore the cursor and block.
-          iterator = blockWithInvoke.listIterator();
+          iterator = blockWithInvoke.listIterator().recordChangesToMetadata(code);
           assert iterator.peekNext() == invoke;
           iterator.next();
         } else {
@@ -303,11 +304,12 @@
             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());
+          iterator =
+              block.listIterator(block.getInstructions().size()).recordChangesToMetadata(code);
           iterator.previous();
           iterator.add(decoupled);
           // Restore the cursor and block.
-          iterator = blockWithInvoke.listIterator();
+          iterator = blockWithInvoke.listIterator().recordChangesToMetadata(code);
           assert iterator.peekNext() == invoke;
           iterator.next();
         } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index d32606d..92c8013 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.UpdatableIRMetadata;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -138,9 +139,7 @@
             null,
             blocks,
             new ValueNumberGenerator(),
-            false,
-            false,
-            false,
+            UpdatableIRMetadata.unknown(),
             Origin.unknown());
     PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appView, code));
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 184dce2..1007ea6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
 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;
@@ -78,9 +79,7 @@
             null,
             blocks,
             new ValueNumberGenerator(),
-            false,
-            false,
-            false,
+            IRMetadata.unknown(),
             Origin.unknown());
     CodeRewriter.collapseTrivialGotos(code);
     assertTrue(code.entryBlock().isTrivialGoto());
@@ -165,9 +164,7 @@
             null,
             blocks,
             new ValueNumberGenerator(),
-            false,
-            false,
-            false,
+            IRMetadata.unknown(),
             Origin.unknown());
     CodeRewriter.collapseTrivialGotos(code);
     assertTrue(block0.getInstructions().get(1).isIf());