Track initialized classes on normal exits

Bug: 123557989
Change-Id: Ief52cdcb4e1f1186811c5f74a9874316e9cdf301
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 6cbbece..85bc4cb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -47,10 +47,12 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableSet;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
@@ -822,6 +824,7 @@
   public static class DefaultOptimizationInfoImpl implements OptimizationInfo {
     public static final OptimizationInfo DEFAULT_INSTANCE = new DefaultOptimizationInfoImpl();
 
+    public static Set<DexType> UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT = ImmutableSet.of();
     public static int UNKNOWN_RETURNED_ARGUMENT = -1;
     public static boolean UNKNOWN_NEVER_RETURNS_NULL = false;
     public static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
@@ -842,6 +845,11 @@
     private DefaultOptimizationInfoImpl() {}
 
     @Override
+    public Set<DexType> getInitializedClassesOnNormalExit() {
+      return UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
+    }
+
+    @Override
     public TrivialInitializer getTrivialInitializerInfo() {
       return UNKNOWN_TRIVIAL_INITIALIZER;
     }
@@ -963,6 +971,8 @@
 
   public static class OptimizationInfoImpl implements UpdatableOptimizationInfo {
 
+    private Set<DexType> initializedClassesOnNormalExit =
+        DefaultOptimizationInfoImpl.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
     private int returnedArgument = DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_ARGUMENT;
     private boolean mayHaveSideEffects = DefaultOptimizationInfoImpl.UNKNOWN_MAY_HAVE_SIDE_EFFECTS;
     private boolean neverReturnsNull = DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NULL;
@@ -1035,6 +1045,11 @@
     }
 
     @Override
+    public Set<DexType> getInitializedClassesOnNormalExit() {
+      return initializedClassesOnNormalExit;
+    }
+
+    @Override
     public TrivialInitializer getTrivialInitializerInfo() {
       return trivialInitializerInfo;
     }
@@ -1184,6 +1199,11 @@
     }
 
     @Override
+    public void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) {
+      this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
+    }
+
+    @Override
     public void markReturnsArgument(int argument) {
       assert argument >= 0;
       assert returnedArgument == -1 || returnedArgument == argument;
diff --git a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
index 33b5f63..98f30cb 100644
--- a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
 import java.util.BitSet;
+import java.util.Set;
 
 public interface OptimizationInfo {
 
@@ -41,6 +42,8 @@
 
   ClassInlinerEligibility getClassInlinerEligibility();
 
+  Set<DexType> getInitializedClassesOnNormalExit();
+
   TrivialInitializer getTrivialInitializerInfo();
 
   boolean isInitializerEnablingJavaAssertions();
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
index 8550abb..f982289 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
@@ -7,8 +7,12 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import java.util.BitSet;
+import java.util.Set;
 
 public interface UpdatableOptimizationInfo extends OptimizationInfo {
+
+  void markInitializesClassesOnNormalExit(Set<DexType> initializedClasses);
+
   void markReturnsArgument(int argument);
 
   void markReturnsConstantNumber(long value);
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 ab803ee..f77d9428d 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
@@ -9,6 +9,9 @@
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -23,6 +26,7 @@
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -31,8 +35,12 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.Iterator;
+import java.util.Set;
 
 /**
  * Analysis that given an instruction determines if a given type is guaranteed to be class
@@ -85,6 +93,7 @@
   }
 
   public boolean isClassDefinitelyLoadedBeforeInstruction(DexType type, Instruction instruction) {
+    DexType context = code.method.method.holder;
     BasicBlock block = instruction.getBlock();
 
     // Visit the instructions in `block` prior to `instruction`.
@@ -94,6 +103,7 @@
       }
       if (previous.definitelyTriggersClassInitialization(
           type,
+          context,
           appView,
           Query.DIRECTLY_OR_INDIRECTLY,
           // The given instruction is only reached if none of the instructions in the same
@@ -114,7 +124,7 @@
       while (instructionIterator.hasNext()) {
         Instruction previous = instructionIterator.next();
         if (previous.definitelyTriggersClassInitialization(
-            type, appView, Query.DIRECTLY_OR_INDIRECTLY, assumption)) {
+            type, context, appView, Query.DIRECTLY_OR_INDIRECTLY, assumption)) {
           return true;
         }
         if (dominator.hasCatchHandlers() && previous.instructionTypeCanThrow()) {
@@ -278,7 +288,8 @@
           return false;
         }
       }
-      return isTypeInitializedBy(type, instruction.getField().holder, appView, mode);
+      DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
+      return field != null && isTypeInitializedBy(type, field, appView, mode);
     }
 
     public static boolean forInvokeDirect(
@@ -293,25 +304,14 @@
           return false;
         }
       }
-      return isTypeInitializedBy(type, instruction.getInvokedMethod().holder, appView, mode);
+      DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
+      return method != null && isTypeInitializedBy(type, method, appView, mode);
     }
 
-    public static boolean forInvokeStatic(
-        InvokeStatic instruction,
+    public static boolean forInvokeInterface(
+        InvokeInterface instruction,
         DexType type,
-        AppView<? extends AppInfo> appView,
-        Query mode,
-        AnalysisAssumption assumption) {
-      if (assumption == AnalysisAssumption.NONE) {
-        // Class initialization may fail with ExceptionInInitializerError.
-        return false;
-      }
-      return isTypeInitializedBy(type, instruction.getInvokedMethod().holder, appView, mode);
-    }
-
-    public static boolean forInvokeSuper(
-        InvokeSuper instruction,
-        DexType type,
+        DexType context,
         AppView<? extends AppInfo> appView,
         Query mode,
         AnalysisAssumption assumption) {
@@ -327,6 +327,64 @@
         // TODO(christofferqa): We can do better if there is a unique target.
         return false;
       }
+      if (appView.appInfo().hasLiveness()) {
+        AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+        DexEncodedMethod singleTarget =
+            instruction.lookupSingleTarget(appInfoWithLiveness, context);
+        if (singleTarget != null) {
+          return isTypeInitializedBy(type, singleTarget, appView, mode);
+        }
+      }
+      DexMethod method = instruction.getInvokedMethod();
+      ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+      if (!resolutionResult.hasSingleTarget()) {
+        return false;
+      }
+      DexType holder = resolutionResult.asSingleTarget().method.holder;
+      return appView.isSubtype(holder, type).isTrue();
+    }
+
+    public static boolean forInvokeStatic(
+        InvokeStatic instruction,
+        DexType type,
+        AppView<? extends AppInfo> appView,
+        Query mode,
+        AnalysisAssumption assumption) {
+      if (assumption == AnalysisAssumption.NONE) {
+        // Class initialization may fail with ExceptionInInitializerError.
+        return false;
+      }
+      DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
+      return method != null && isTypeInitializedBy(type, method, appView, mode);
+    }
+
+    public static boolean forInvokeSuper(
+        InvokeSuper instruction,
+        DexType type,
+        DexType context,
+        AppView<? extends AppInfo> appView,
+        Query mode,
+        AnalysisAssumption assumption) {
+      if (assumption == AnalysisAssumption.NONE) {
+        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+          // If the receiver is null we cannot be sure that the holder has been initialized.
+          return false;
+        }
+      }
+      if (mode == Query.DIRECTLY) {
+        // We cannot ensure exactly which class is being loaded because it depends on the runtime
+        // type of the receiver.
+        // TODO(christofferqa): We can do better if there is a unique target.
+        return false;
+      }
+      if (appView.appInfo().hasLiveness()) {
+        AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+        DexEncodedMethod singleTarget =
+            instruction.lookupSingleTarget(appInfoWithLiveness, context);
+        if (singleTarget != null) {
+          return isTypeInitializedBy(type, singleTarget, appView, mode);
+        }
+      }
       DexMethod method = instruction.getInvokedMethod();
       DexClass enclosingClass = appView.definitionFor(method.holder);
       if (enclosingClass == null) {
@@ -347,6 +405,7 @@
     public static boolean forInvokeVirtual(
         InvokeVirtual instruction,
         DexType type,
+        DexType context,
         AppView<? extends AppInfo> appView,
         Query mode,
         AnalysisAssumption assumption) {
@@ -362,6 +421,14 @@
         // TODO(christofferqa): We can do better if there is a unique target.
         return false;
       }
+      if (appView.appInfo().hasLiveness()) {
+        AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+        DexEncodedMethod singleTarget =
+            instruction.lookupSingleTarget(appInfoWithLiveness, context);
+        if (singleTarget != null) {
+          return isTypeInitializedBy(type, singleTarget, appView, mode);
+        }
+      }
       DexMethod method = instruction.getInvokedMethod();
       ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
       if (!resolutionResult.hasSingleTarget()) {
@@ -377,7 +444,12 @@
         AppView<? extends AppInfo> appView,
         Query mode,
         AnalysisAssumption assumption) {
-      return isTypeInitializedBy(type, instruction.clazz, appView, mode);
+      if (assumption == AnalysisAssumption.NONE) {
+        // Instruction may throw.
+        return false;
+      }
+      DexClass clazz = appView.definitionFor(instruction.clazz);
+      return clazz != null && isTypeInitializedBy(type, clazz, appView, mode);
     }
 
     public static boolean forStaticGet(
@@ -409,18 +481,76 @@
         // Class initialization may fail with ExceptionInInitializerError.
         return false;
       }
-      return isTypeInitializedBy(type, instruction.getField().holder, appView, mode);
+      DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
+      return field != null && isTypeInitializedBy(type, field, appView, mode);
     }
 
     private static boolean isTypeInitializedBy(
         DexType typeToBeInitialized,
-        DexType typeKnownToBeInitialized,
+        DexDefinition definition,
         AppView<? extends AppInfo> appView,
         Query mode) {
       if (mode == Query.DIRECTLY) {
-        return typeKnownToBeInitialized == typeToBeInitialized;
+        if (definition.isDexClass()) {
+          return definition.asDexClass().type == typeToBeInitialized;
+        }
+        if (definition.isDexEncodedField()) {
+          return definition.asDexEncodedField().field.holder == typeToBeInitialized;
+        }
+        if (definition.isDexEncodedMethod()) {
+          return definition.asDexEncodedMethod().method.holder == typeToBeInitialized;
+        }
+        assert false;
+        return false;
+      }
+
+      Set<DexType> visited = Sets.newIdentityHashSet();
+      Deque<DexType> worklist = new ArrayDeque<>();
+
+      if (definition.isDexClass()) {
+        DexClass clazz = definition.asDexClass();
+        enqueue(clazz.type, visited, worklist);
+      } else if (definition.isDexEncodedField()) {
+        DexEncodedField field = definition.asDexEncodedField();
+        enqueue(field.field.holder, visited, worklist);
+      } else if (definition.isDexEncodedMethod()) {
+        DexEncodedMethod method = definition.asDexEncodedMethod();
+        enqueue(method.method.holder, visited, worklist);
+        enqueueInitializedClassesOnNormalExit(method, visited, worklist);
       } else {
-        return appView.isSubtype(typeKnownToBeInitialized, typeToBeInitialized).isTrue();
+        assert false;
+      }
+
+      while (!worklist.isEmpty()) {
+        DexType typeKnownToBeInitialized = worklist.removeFirst();
+        assert visited.contains(typeKnownToBeInitialized);
+
+        if (appView.isSubtype(typeKnownToBeInitialized, typeToBeInitialized).isTrue()) {
+          return true;
+        }
+
+        DexClass clazz = appView.definitionFor(typeKnownToBeInitialized);
+        if (clazz != null) {
+          DexEncodedMethod classInitializer = clazz.getClassInitializer();
+          if (classInitializer != null) {
+            enqueueInitializedClassesOnNormalExit(classInitializer, visited, worklist);
+          }
+        }
+      }
+
+      return false;
+    }
+
+    private static void enqueue(DexType type, Set<DexType> visited, Deque<DexType> worklist) {
+      if (type.isClassType() && visited.add(type)) {
+        worklist.add(type);
+      }
+    }
+
+    private static void enqueueInitializedClassesOnNormalExit(
+        DexEncodedMethod method, Set<DexType> visited, Deque<DexType> worklist) {
+      for (DexType type : method.getOptimizationInfo().getInitializedClassesOnNormalExit()) {
+        enqueue(type, visited, worklist);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
new file mode 100644
index 0000000..295c39d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
@@ -0,0 +1,152 @@
+// 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.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DefaultInstructionVisitor;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+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.Invoke;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An analysis that given a method returns a set of types that are guaranteed to be initialized by
+ * the method on all normal exits of the given method.
+ */
+public class InitializedClassesOnNormalExitAnalysis {
+
+  public static Set<DexType> computeInitializedClassesOnNormalExit(
+      AppView<AppInfoWithLiveness> appView, IRCode code) {
+    DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
+    Visitor visitor = new Visitor(appView, code.method.method.holder);
+    for (BasicBlock dominator : dominatorTree.normalExitDominatorBlocks()) {
+      if (dominator.hasCatchHandlers()) {
+        // When determining which classes that are guaranteed to be initialized from a given
+        // instruction, we assume that the given instruction does not throw. Therefore, we skip
+        // blocks that have a catch handler.
+        continue;
+      }
+
+      for (Instruction instruction : dominator.getInstructions()) {
+        instruction.accept(visitor);
+      }
+    }
+    return visitor.build();
+  }
+
+  private static class Visitor extends DefaultInstructionVisitor<Void> {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final DexType context;
+    private final Set<DexType> initializedClassesOnNormalExit = Sets.newIdentityHashSet();
+
+    Visitor(AppView<AppInfoWithLiveness> appView, DexType context) {
+      this.appView = appView;
+      this.context = context;
+    }
+
+    Set<DexType> build() {
+      return Collections.unmodifiableSet(initializedClassesOnNormalExit);
+    }
+
+    private void markInitializedOnNormalExit(Iterable<DexType> knownToBeInitialized) {
+      knownToBeInitialized.forEach(this::markInitializedOnNormalExit);
+    }
+
+    private void markInitializedOnNormalExit(DexType knownToBeInitialized) {
+      if (knownToBeInitialized == context) {
+        // Do not record that the given method causes its own holder to be initialized, since this
+        // is trivial.
+        return;
+      }
+      DexClass clazz = appView.definitionFor(knownToBeInitialized);
+      if (clazz == null) {
+        return;
+      }
+      if (!clazz.isProgramClass()) {
+        // Only mark program classes as being initialized on normal exits.
+        return;
+      }
+      if (!clazz.classInitializationMayHaveSideEffects(appView)) {
+        // Only mark classes that actually have side effects during class initialization.
+        return;
+      }
+      List<DexType> subsumedByKnownToBeInitialized = null;
+      for (DexType alreadyKnownToBeInitialized : initializedClassesOnNormalExit) {
+        if (appView.isSubtype(alreadyKnownToBeInitialized, knownToBeInitialized).isTrue()) {
+          // The fact that there is a subtype B of the given type A, which is known to be
+          // initialized, implies that the given type A is also be initialized. Therefore, we do not
+          // add this type into the set of classes that have been initialized on all normal exits.
+          return;
+        }
+
+        if (appView.isSubtype(knownToBeInitialized, alreadyKnownToBeInitialized).isTrue()) {
+          if (subsumedByKnownToBeInitialized == null) {
+            subsumedByKnownToBeInitialized = new ArrayList<>();
+          }
+          subsumedByKnownToBeInitialized.add(alreadyKnownToBeInitialized);
+        }
+      }
+      initializedClassesOnNormalExit.add(knownToBeInitialized);
+      if (subsumedByKnownToBeInitialized != null) {
+        initializedClassesOnNormalExit.removeAll(subsumedByKnownToBeInitialized);
+      }
+    }
+
+    @Override
+    public Void handleFieldInstruction(FieldInstruction instruction) {
+      DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
+      if (field != null) {
+        if (field.field.holder.isClassType()) {
+          markInitializedOnNormalExit(field.field.holder);
+        } else {
+          assert false : "Expected holder of field type to be a class type";
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public Void handleInvoke(Invoke instruction) {
+      if (instruction.isInvokeMethod()) {
+        InvokeMethod invoke = instruction.asInvokeMethod();
+        DexMethod method = invoke.getInvokedMethod();
+        if (method.holder.isClassType()) {
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView.appInfo(), context);
+          if (singleTarget != null) {
+            markInitializedOnNormalExit(singleTarget.method.holder);
+            markInitializedOnNormalExit(
+                singleTarget.getOptimizationInfo().getInitializedClassesOnNormalExit());
+          } else {
+            markInitializedOnNormalExit(method.holder);
+          }
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public Void visit(NewInstance instruction) {
+      markInitializedOnNormalExit(instruction.clazz);
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Add.java b/src/main/java/com/android/tools/r8/ir/code/Add.java
index 2a8da38..513798f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Add.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Add.java
@@ -23,6 +23,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isCommutative() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
index d419cbb..a3e9dba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -20,6 +20,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
     // This instruction may never be considered dead as it must remain.
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
index 7f8c6eb..a72c180 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index c36ee42..6f4eef9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -20,6 +20,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
     // This instruction may never be considered dead as it must remain.
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/And.java b/src/main/java/com/android/tools/r8/ir/code/And.java
index 73d3158..cc782ff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/And.java
+++ b/src/main/java/com/android/tools/r8/ir/code/And.java
@@ -19,6 +19,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isAnd() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 8cbf90d..7efb914 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -27,6 +27,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean canBeDeadCode(AppView<? extends AppInfo> appview, IRCode code) {
     // Never remove argument instructions. That would change the signature of the method.
     // TODO(b/65810338): If we can tell that a method never uses an argument we might be able to
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index f5e6e6c..3e2af3d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -39,6 +39,11 @@
     this.type = type;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 97a610f..061aa53 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -24,6 +24,11 @@
     super(dest, array);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index f0be051..a76bf42 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -43,6 +43,11 @@
     this.type = type;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value array() {
     return inValues.get(ARRAY_INDEX);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 9aff46e..0c1396f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -32,6 +32,11 @@
     this.type = type;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public DexType getType() {
     return type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 8ad8d4b..9742a20 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -38,6 +38,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isCommutative() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 83bbb0f..5deee15 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -28,6 +28,11 @@
     this.clazz = clazz;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public static ConstClass copyOf(IRCode code, ConstClass original) {
     Value newValue =
         new Value(
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 40c879f..166a121 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -27,6 +27,11 @@
     this.methodHandle = methodHandle;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public static ConstMethodHandle copyOf(IRCode code, ConstMethodHandle original) {
     Value newValue =
         new Value(
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index f9eee1b..f2a2f57 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -25,6 +25,11 @@
     this.methodType = methodType;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public static ConstMethodType copyOf(IRCode code, ConstMethodType original) {
     Value newValue =
         new Value(
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 4e4e1e6..8c1be98 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -42,6 +42,11 @@
     this.value = value;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
     Value newValue = new Value(
         code.valueNumberGenerator.next(),
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 11b9a3d..2db5a33 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -29,6 +29,11 @@
     this.throwingInfo = throwingInfo;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public static ConstString copyOf(IRCode code, ConstString original) {
     Value newValue =
         new Value(code.valueNumberGenerator.next(),
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 8697b79..e9afbe3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isDebugLocalRead() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalUninitialized.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalUninitialized.java
index 565204f..7feb7f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalUninitialized.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalUninitialized.java
@@ -22,6 +22,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isDebugLocalUninitialized() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index de1b6fb..a325d9b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -32,6 +32,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isDebugLocalWrite() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 4f097ff..560d524 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -30,6 +30,11 @@
     this.starting = starting;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Int2ReferenceMap<DebugLocalInfo> getEnding() {
     return ending;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 02957b6..8e61fc3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isDebugPosition() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
new file mode 100644
index 0000000..bcbae23
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -0,0 +1,351 @@
+// 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 DefaultInstructionVisitor<T> implements InstructionVisitor<T> {
+
+  public T handleFieldInstruction(FieldInstruction instruction) {
+    return null;
+  }
+
+  public T handleInvoke(Invoke instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Add instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(AlwaysMaterializingDefinition instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(AlwaysMaterializingNop instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(AlwaysMaterializingUser instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(And instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Argument instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ArrayGet instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ArrayLength instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ArrayPut instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(CheckCast instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Cmp instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ConstClass instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ConstMethodHandle instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ConstMethodType instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ConstNumber instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(ConstString instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(DebugLocalRead instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(DebugLocalsChange instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(DebugLocalUninitialized instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(DebugLocalWrite instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(DebugPosition instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(DexItemBasedConstString instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Div instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Dup instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Dup2 instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Goto instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(If instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Inc instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(InstanceGet instruction) {
+    return handleFieldInstruction(instruction);
+  }
+
+  @Override
+  public T visit(InstanceOf instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(InstancePut instruction) {
+    return handleFieldInstruction(instruction);
+  }
+
+  @Override
+  public T visit(InvokeCustom instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokeDirect instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokeInterface instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokeMultiNewArray instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokeNewArray instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokePolymorphic instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokeStatic instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokeSuper instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(InvokeVirtual instruction) {
+    return handleInvoke(instruction);
+  }
+
+  @Override
+  public T visit(Load instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Monitor instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Move instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(MoveException instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Mul instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Neg instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(NewArrayEmpty instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(NewArrayFilledData instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(NewInstance instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(NonNull instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Not instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(NumberConversion instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Or instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Pop instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Rem instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Return instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Shl instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Shr instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(StaticGet instruction) {
+    return handleFieldInstruction(instruction);
+  }
+
+  @Override
+  public T visit(StaticPut instruction) {
+    return handleFieldInstruction(instruction);
+  }
+
+  @Override
+  public T visit(Store instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Sub instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Swap instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Switch instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Throw instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Ushr instruction) {
+    return null;
+  }
+
+  @Override
+  public T visit(Xor instruction) {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index d5c8c82..0fa50e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -39,6 +39,11 @@
     this.throwingInfo = throwingInfo;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
     return new DexItemBasedConstString(
         newValue, original.getItem(), original.throwingInfo, original.classNameComputationInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index 60daec0..ba55d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -25,6 +25,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isDiv() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 960ea92..72c64a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -169,10 +169,6 @@
    * Iteration order is always the immediate dominator of the previously returned block. The
    * iteration starts by returning <code>dominated</code>.
    */
-  public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
-    return dominatorBlocks(dominated, Inclusive.YES);
-  }
-
   public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated, Inclusive inclusive) {
     assert !obsolete;
     return () -> {
@@ -211,7 +207,9 @@
 
   public Iterable<BasicBlock> normalExitDominatorBlocks() {
     assert !obsolete;
-    return dominatorBlocks(normalExitBlock);
+    // Do not include the `normalExitBlock`, since this is a synthetic block created here in the
+    // DominatorTree.
+    return dominatorBlocks(normalExitBlock, Inclusive.NO);
   }
 
   public BasicBlock[] getSortedBlocks() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 89542a7..0a09d78 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -26,6 +26,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public void setOutValue(Value value) {
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index fcb5b8e..6564fbe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -40,6 +40,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public void setOutValue(Value value) {
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 0c4bd04..a14098d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -22,6 +22,11 @@
     setBlock(block);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public BasicBlock getTarget() {
     assert getBlock().exit() == this;
     List<BasicBlock> successors = getBlock().getSuccessors();
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 25d1fc4..e5ac27f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -79,6 +79,11 @@
     this.type = type;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public boolean isZeroTest() {
     return inValues.size() == 1;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Inc.java b/src/main/java/com/android/tools/r8/ir/code/Inc.java
index 3455108..d92eb4d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Inc.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Inc.java
@@ -28,6 +28,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   protected void addInValue(Value value) {
     // Overriding IR addInValue since userinfo is cleared.
     assert !value.hasUsersInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 542d635..86d549d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -41,6 +41,11 @@
     super(field, dest, object);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
@@ -197,6 +202,7 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index e2cfeb3..b6dcc21 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -25,6 +25,11 @@
     this.type = type;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public DexType type() {
     return type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index fd9e819..e4b062f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -39,6 +39,11 @@
     assert value().verifyCompatible(ValueType.fromDexType(field.type));
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value object() {
     return inValues.get(0);
   }
@@ -171,6 +176,7 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
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 f845dc5..85ccb4e 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
@@ -61,6 +61,8 @@
     setOutValue(outValue);
   }
 
+  public abstract <T> T accept(InstructionVisitor<T> visitor);
+
   public final Position getPosition() {
     assert position != null;
     return position;
@@ -1262,6 +1264,7 @@
    */
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
new file mode 100644
index 0000000..ec7ea00
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -0,0 +1,142 @@
+// 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 interface InstructionVisitor<T> {
+
+  T visit(Add instruction);
+
+  T visit(AlwaysMaterializingDefinition instruction);
+
+  T visit(AlwaysMaterializingNop instruction);
+
+  T visit(AlwaysMaterializingUser instruction);
+
+  T visit(And instruction);
+
+  T visit(Argument instruction);
+
+  T visit(ArrayGet instruction);
+
+  T visit(ArrayLength instruction);
+
+  T visit(ArrayPut instruction);
+
+  T visit(CheckCast instruction);
+
+  T visit(Cmp instruction);
+
+  T visit(ConstClass instruction);
+
+  T visit(ConstMethodHandle instruction);
+
+  T visit(ConstMethodType instruction);
+
+  T visit(ConstNumber instruction);
+
+  T visit(ConstString instruction);
+
+  T visit(DebugLocalRead instruction);
+
+  T visit(DebugLocalsChange instruction);
+
+  T visit(DebugLocalUninitialized instruction);
+
+  T visit(DebugLocalWrite instruction);
+
+  T visit(DebugPosition instruction);
+
+  T visit(DexItemBasedConstString instruction);
+
+  T visit(Div instruction);
+
+  T visit(Dup instruction);
+
+  T visit(Dup2 instruction);
+
+  T visit(Goto instruction);
+
+  T visit(If instruction);
+
+  T visit(Inc instruction);
+
+  T visit(InstanceGet instruction);
+
+  T visit(InstanceOf instruction);
+
+  T visit(InstancePut instruction);
+
+  T visit(InvokeCustom instruction);
+
+  T visit(InvokeDirect instruction);
+
+  T visit(InvokeInterface instruction);
+
+  T visit(InvokeMultiNewArray instruction);
+
+  T visit(InvokeNewArray instruction);
+
+  T visit(InvokePolymorphic instruction);
+
+  T visit(InvokeStatic instruction);
+
+  T visit(InvokeSuper instruction);
+
+  T visit(InvokeVirtual instruction);
+
+  T visit(Load instruction);
+
+  T visit(Monitor instruction);
+
+  T visit(Move instruction);
+
+  T visit(MoveException instruction);
+
+  T visit(Mul instruction);
+
+  T visit(Neg instruction);
+
+  T visit(NewArrayEmpty instruction);
+
+  T visit(NewArrayFilledData instruction);
+
+  T visit(NewInstance instruction);
+
+  T visit(NonNull instruction);
+
+  T visit(Not instruction);
+
+  T visit(NumberConversion instruction);
+
+  T visit(Or instruction);
+
+  T visit(Pop instruction);
+
+  T visit(Rem instruction);
+
+  T visit(Return instruction);
+
+  T visit(Shl instruction);
+
+  T visit(Shr instruction);
+
+  T visit(StaticGet instruction);
+
+  T visit(StaticPut instruction);
+
+  T visit(Store instruction);
+
+  T visit(Sub instruction);
+
+  T visit(Swap instruction);
+
+  T visit(Switch instruction);
+
+  T visit(Throw instruction);
+
+  T visit(Ushr instruction);
+
+  T visit(Xor instruction);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 3d13f750..6ab4e1e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -28,6 +28,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public DexType getReturnType() {
     return callSite.methodProto.returnType;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 53d3c8a..b214355 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -47,6 +47,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public Type getType() {
     return Type.DIRECT;
   }
@@ -134,6 +139,7 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index c790cab..12aa72d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -5,10 +5,15 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeInterfaceRange;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -26,6 +31,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public Type getType() {
     return Type.INTERFACE;
   }
@@ -98,4 +108,15 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfInvoke(Opcodes.INVOKEINTERFACE, getInvokedMethod(), true));
   }
+
+  @Override
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      DexType context,
+      AppView<? extends AppInfo> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    return ClassInitializationAnalysis.InstructionUtils.forInvokeInterface(
+        this, clazz, context, appView, mode, assumption);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 13a8473..3b9ce30 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -28,6 +28,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isInvokeMultiNewArray() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index f89ccf6..37e3bad 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -29,6 +29,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public DexType getReturnType() {
     return getArrayType();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 54e3065..2a57198 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -33,6 +33,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public DexType getReturnType() {
     return proto.returnType;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index d25f065..4298299 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -43,6 +43,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public Type getType() {
     return Type.STATIC;
   }
@@ -127,6 +132,7 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 4b0a773..246f203 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -34,6 +34,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public Type getType() {
     return Type.SUPER;
   }
@@ -116,10 +121,11 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
     return ClassInitializationAnalysis.InstructionUtils.forInvokeSuper(
-        this, clazz, appView, mode, assumption);
+        this, clazz, context, appView, mode, assumption);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index faffec2..115ecb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -31,6 +31,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public Type getType() {
     return Type.VIRTUAL;
   }
@@ -107,11 +112,12 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
     return ClassInitializationAnalysis.InstructionUtils.forInvokeVirtual(
-        this, clazz, appView, mode, assumption);
+        this, clazz, context, appView, mode, assumption);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 4229b41..2245aa9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -23,6 +23,11 @@
     super(dest, src);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value src() {
     return inValues.get(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 4f650f7..60b99f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -31,6 +31,11 @@
     this.type = type;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value object() {
     return inValues.get(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 53c0134..e9f798a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -24,6 +24,11 @@
     super(dest, src);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 09619fb..1ffcd78 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -27,6 +27,11 @@
     this.options = options;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Mul.java b/src/main/java/com/android/tools/r8/ir/code/Mul.java
index ebe12f8..b6b3ece 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Mul.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Mul.java
@@ -23,6 +23,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isCommutative() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 856e2e0..f0bc717 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -28,6 +28,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean canBeFolded() {
     return (type == NumericType.INT || type == NumericType.LONG || type == NumericType.FLOAT
             || type == NumericType.DOUBLE)
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 641b77e..0828653 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -28,6 +28,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public String toString() {
     return super.toString() + " " + type.toString();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 3151a94..9104992 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -33,6 +33,11 @@
     this.data = data;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value src() {
     return inValues.get(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 3f97ba5..5fb8a0e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -34,6 +34,11 @@
     this.clazz = clazz;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
@@ -116,6 +121,7 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 80bd6d9..4074c11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -25,6 +25,11 @@
     this.origin = origin;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 013b219..c5d9970 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -26,6 +26,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean canBeFolded() {
     return source().isConstant();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index 2b38cba..4ca038e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -38,6 +38,11 @@
     this.to = to;
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public boolean isLongToIntConversion() {
     return from == NumericType.LONG && to == NumericType.INT;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Or.java b/src/main/java/com/android/tools/r8/ir/code/Or.java
index fd4f204..67b730f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Or.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Or.java
@@ -18,6 +18,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isOr() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 32bd532..75e1762 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   protected void addInValue(Value value) {
     if (value.hasUsersInfo()) {
       super.addInValue(value);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Rem.java b/src/main/java/com/android/tools/r8/ir/code/Rem.java
index bd838bc..f0eb1ca 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Rem.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Rem.java
@@ -25,6 +25,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isRem() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 7d845ca..d1073bc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -28,6 +28,11 @@
     super(null, value);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public boolean isReturnVoid() {
     return inValues.size() == 0;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shl.java b/src/main/java/com/android/tools/r8/ir/code/Shl.java
index d9d0be0..719abf0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shl.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shl.java
@@ -18,6 +18,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   boolean fitsInDexInstruction(Value value) {
     // The shl instruction only has the /lit8 variant.
     return fitsInLit8Instruction(value);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shr.java b/src/main/java/com/android/tools/r8/ir/code/Shr.java
index c930d1d..036bdbc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shr.java
@@ -18,6 +18,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   boolean fitsInDexInstruction(Value value) {
     // The shr instruction only has the /lit8 variant.
     return fitsInLit8Instruction(value);
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 25d88fb..328b2f2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -39,6 +39,11 @@
     super(field, dest, (Value) null);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value dest() {
     return outValue;
   }
@@ -190,6 +195,7 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index a87c0bb..a87692d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -34,6 +34,11 @@
     super(field, null, source);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value inValue() {
     assert inValues.size() == 1;
     return inValues.get(0);
@@ -158,6 +163,7 @@
   @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
+      DexType context,
       AppView<? extends AppInfo> appView,
       Query mode,
       AnalysisAssumption assumption) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 2058ba0..dc34dd1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -24,6 +24,11 @@
     super(dest, src);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value src() {
     return inValues.get(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Sub.java b/src/main/java/com/android/tools/r8/ir/code/Sub.java
index 7e7a9f8..74c8517 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Sub.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Sub.java
@@ -28,6 +28,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isCommutative() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 3c623d6..a43979c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -29,6 +29,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public void setOutValue(Value value) {
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 2f82b0b..213ff92 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -41,6 +41,11 @@
     assert valid();
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   private boolean valid() {
     assert keys.length <= Constants.U16BIT_MAX;
     // Keys must be acceding, and cannot target the fallthrough.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 23bb76c..6c91191 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -20,6 +20,11 @@
     super(null, exception);
   }
 
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
   public Value exception() {
     return inValues.get(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Ushr.java b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
index 65c932c..b7a5406 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Ushr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
@@ -18,6 +18,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   boolean fitsInDexInstruction(Value value) {
     // The ushr instruction only has the /lit8 variant.
     return fitsInLit8Instruction(value);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Xor.java b/src/main/java/com/android/tools/r8/ir/code/Xor.java
index f3b8858..ec2d8db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Xor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Xor.java
@@ -18,6 +18,11 @@
   }
 
   @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
   public boolean isXor() {
     return true;
   }
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 bb3e80e..57562d8 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -1063,6 +1064,7 @@
         computeNonNullParamHints(feedback, method, code);
       }
 
+      computeInitializedClassesOnNormalExit(feedback, method, code);
       computeMayHaveSideEffects(feedback, method, code);
     }
 
@@ -1155,6 +1157,19 @@
     }
   }
 
+  private void computeInitializedClassesOnNormalExit(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+    if (options.enableInitializedClassesAnalysis && appView.appInfo().hasLiveness()) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+      Set<DexType> initializedClasses =
+          InitializedClassesOnNormalExitAnalysis.computeInitializedClassesOnNormalExit(
+              appViewWithLiveness, code);
+      if (initializedClasses != null && !initializedClasses.isEmpty()) {
+        feedback.methodInitializesClassesOnNormalExit(method, initializedClasses);
+      }
+    }
+  }
+
   private void computeMayHaveSideEffects(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     if (options.enableSideEffectAnalysis
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index 4cef96f..3669369 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -8,11 +8,16 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
+import java.util.Set;
 
 public interface OptimizationFeedback {
+  void methodInitializesClassesOnNormalExit(
+      DexEncodedMethod method, Set<DexType> initializedClasses);
+
   void methodReturnsArgument(DexEncodedMethod method, int argument);
   void methodReturnsConstantNumber(DexEncodedMethod method, long value);
   void methodReturnsConstantString(DexEncodedMethod method, DexString value);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
index 7382f9a..1042a63e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.UpdatableOptimizationInfo;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -15,6 +16,7 @@
 import java.util.BitSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
 
 public class OptimizationFeedbackDelayed implements OptimizationFeedback {
 
@@ -35,6 +37,12 @@
   }
 
   @Override
+  public synchronized void methodInitializesClassesOnNormalExit(
+      DexEncodedMethod method, Set<DexType> initializedClasses) {
+    getOptimizationInfoForUpdating(method).markInitializesClassesOnNormalExit(initializedClasses);
+  }
+
+  @Override
   public synchronized void methodReturnsArgument(DexEncodedMethod method, int argument) {
     getOptimizationInfoForUpdating(method).markReturnsArgument(argument);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index a2a8bf3..2e5f0dd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -8,13 +8,19 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
+import java.util.Set;
 
 public class OptimizationFeedbackIgnore implements OptimizationFeedback {
 
   @Override
+  public void methodInitializesClassesOnNormalExit(
+      DexEncodedMethod method, Set<DexType> initializedClasses) {}
+
+  @Override
   public void methodReturnsArgument(DexEncodedMethod method, int argument) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
index a908798..225a1e7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -8,13 +8,21 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
+import java.util.Set;
 
 public class OptimizationFeedbackSimple implements OptimizationFeedback {
 
   @Override
+  public void methodInitializesClassesOnNormalExit(
+      DexEncodedMethod method, Set<DexType> initializedClasses) {
+    // Ignored.
+  }
+
+  @Override
   public void methodReturnsArgument(DexEncodedMethod method, int argument) {
     // Ignored.
   }
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 dbe8837..52284d1 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
@@ -1489,8 +1489,9 @@
     return alwaysTriggerExpectedEffectBeforeAnythingElse(
         code,
         (instruction, it) -> {
+          DexType context = code.method.method.holder;
           if (instruction.definitelyTriggersClassInitialization(
-              clazz, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
+              clazz, context, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
             // In order to preserve class initialization semantic, the exception must not be caught
             // by any handler. Therefore, we must ignore this instruction if it is covered by a
             // catch handler.
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index aad45cd..bca5d8f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -140,6 +140,7 @@
       !Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
   public boolean enableClassInlining = true;
   public boolean enableClassStaticizer = true;
+  public boolean enableInitializedClassesAnalysis = true;
   public boolean enableSideEffectAnalysis = true;
   // TODO(b/120138731): Enable this when it is worthwhile, e.g., combined with Class#forName.
   public boolean enableNameReflectionOptimization = false;
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/initializedclasses/InitializedClassesOnNormalExitAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/initializedclasses/InitializedClassesOnNormalExitAnalysisTest.java
new file mode 100644
index 0000000..7d05b2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/initializedclasses/InitializedClassesOnNormalExitAnalysisTest.java
@@ -0,0 +1,101 @@
+// 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.analysis.initializedclasses;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InitializedClassesOnNormalExitAnalysisTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public InitializedClassesOnNormalExitAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("A.<clinit>()", "A.foo()");
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InitializedClassesOnNormalExitAnalysisTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::verifyInlineableHasBeenInlined)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  private void verifyInlineableHasBeenInlined(CodeInspector inspector) {
+    // Verify that main() only invokes loadA() and println().
+    {
+      ClassSubject classSubject = inspector.clazz(TestClass.class);
+      assertThat(classSubject, isPresent());
+
+      MethodSubject methodSubject = classSubject.mainMethod();
+      assertThat(methodSubject, isPresent());
+
+      assertEquals(
+          2, methodSubject.streamInstructions().filter(InstructionSubject::isInvoke).count());
+    }
+
+    // Verify absence of inlineable().
+    {
+      ClassSubject classSubject = inspector.clazz(A.class);
+      assertThat(classSubject, isPresent());
+
+      MethodSubject methodSubject = classSubject.uniqueMethodWithName("inlineable");
+      assertThat(methodSubject, not(isPresent()));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      loadA();
+      A.inlineable();
+    }
+
+    @NeverInline
+    static void loadA() {
+      A.load();
+    }
+  }
+
+  static class A {
+
+    static {
+      System.out.println("A.<clinit>()");
+    }
+
+    static void load() {}
+
+    static void inlineable() {
+      System.out.println("A.foo()");
+    }
+  }
+}