Merge "Add Vungle to run_on_as_app"
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 8c79ea1..c5cb84e 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -26,6 +26,7 @@
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 
 public class LoadStoreHelper {
@@ -35,7 +36,7 @@
   private final AppInfo appInfo;
 
   private Map<Value, ConstInstruction> clonableConstants = null;
-  private BasicBlock block = null;
+  private ListIterator<BasicBlock> blockIterator = null;
 
   public LoadStoreHelper(IRCode code, TypeVerificationHelper typesHelper, AppInfo appInfo) {
     this.code = code;
@@ -96,16 +97,16 @@
 
   public void insertLoadsAndStores() {
     clonableConstants = new IdentityHashMap<>();
-    for (BasicBlock block : code.blocks) {
-      this.block = block;
-      InstructionListIterator it = block.listIterator();
+    blockIterator = code.blocks.listIterator();
+    while (blockIterator.hasNext()) {
+      InstructionListIterator it = blockIterator.next().listIterator();
       while (it.hasNext()) {
         it.next().insertLoadAndStores(it, this);
       }
       clonableConstants.clear();
     }
     clonableConstants = null;
-    block = null;
+    blockIterator = null;
   }
 
   public void insertPhiMoves(CfRegisterAllocator allocator) {
@@ -170,8 +171,7 @@
     assert !(instruction.outValue() instanceof StackValue);
     if (instruction.isConstInstruction()) {
       ConstInstruction constInstruction = instruction.asConstInstruction();
-      assert block != null;
-      if (canRemoveConstInstruction(constInstruction, block)) {
+      if (canRemoveConstInstruction(constInstruction, instruction.getBlock())) {
         assert !constInstruction.isDexItemBasedConstString()
             || constInstruction.outValue().numberOfUsers() == 1;
         clonableConstants.put(instruction.outValue(), constInstruction);
@@ -180,7 +180,8 @@
         return;
       }
       assert instruction.outValue().isUsed(); // Should have removed it above.
-    } else if (!instruction.outValue().isUsed()) {
+    }
+    if (!instruction.outValue().isUsed()) {
       popOutValue(instruction.outValue(), instruction, it);
       return;
     }
@@ -189,19 +190,40 @@
     Store store = new Store(oldOutValue, newOutValue);
     // Move the debugging-locals liveness pertaining to the instruction to the store.
     instruction.moveDebugValues(store);
-    add(store, instruction, it);
-  }
-
-  public void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
-    StackValue newOutValue = createStackValue(value, 0);
-    instruction.swapOutValue(newOutValue);
-    add(new Pop(newOutValue), instruction, it);
+    BasicBlock storeBlock = instruction.getBlock();
+    // If the block has catch-handlers we are not allowed to modify the locals of the block. If the
+    // instruction is throwing, the action should be moved to a new block - otherwise, the store
+    // should be inserted and the remaining instructions should be moved along with the handlers to
+    // the new block.
+    boolean hasCatchHandlers = instruction.getBlock().hasCatchHandlers();
+    if (hasCatchHandlers && instruction.instructionTypeCanThrow()) {
+      storeBlock = it.split(this.code, this.blockIterator);
+      it = storeBlock.listIterator();
+    }
+    add(store, storeBlock, instruction.getPosition(), it);
+    if (hasCatchHandlers && !instruction.instructionTypeCanThrow()) {
+      it.split(this.code, this.blockIterator);
+      this.blockIterator.previous();
+    }
   }
 
   public void popOutType(DexType type, Instruction instruction, InstructionListIterator it) {
-    StackValue newOutValue = createStackValue(type, 0);
+    popOutValue(createStackValue(type, 0), instruction, it);
+  }
+
+  private void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
+    popOutValue(createStackValue(value, 0), instruction, it);
+  }
+
+  private void popOutValue(
+      StackValue newOutValue, Instruction instruction, InstructionListIterator it) {
+    BasicBlock insertBlock = instruction.getBlock();
+    if (insertBlock.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
+      insertBlock = it.split(this.code, this.blockIterator);
+      it = insertBlock.listIterator();
+    }
     instruction.swapOutValue(newOutValue);
-    add(new Pop(newOutValue), instruction, it);
+    add(new Pop(newOutValue), insertBlock, instruction.getPosition(), it);
   }
 
   private static class PhiMove {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 72c6903..6b9257b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -173,6 +173,8 @@
   public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
   public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
   public final DexString throwableDescriptor = createString("Ljava/lang/Throwable;");
+  public final DexString exceptionInInitializerErrorDescriptor =
+      createString("Ljava/lang/ExceptionInInitializerError;");
   public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
   public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
   public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
@@ -228,6 +230,8 @@
   public final DexType enumType = createType(enumDescriptor);
   public final DexType annotationType = createType(annotationDescriptor);
   public final DexType throwableType = createType(throwableDescriptor);
+  public final DexType exceptionInInitializerErrorType =
+      createType(exceptionInInitializerErrorDescriptor);
   public final DexType classType = createType(classDescriptor);
   public final DexType autoCloseableType = createType(autoCloseableDescriptor);
 
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
new file mode 100644
index 0000000..a0fbb59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -0,0 +1,232 @@
+// 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.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+import com.android.tools.r8.ir.code.DominatorTree.Inclusive;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.Streams;
+import java.util.Iterator;
+
+/**
+ * Analysis that given an instruction determines if a given type is guaranteed to be class
+ * initialized prior the given instruction.
+ */
+public class ClassInitializationAnalysis {
+
+  public enum AnalysisAssumption {
+    INSTRUCTION_DOES_NOT_THROW,
+    NONE
+  }
+
+  public enum Query {
+    DIRECTLY,
+    DIRECTLY_OR_INDIRECTLY
+  }
+
+  private static final ClassInitializationAnalysis TRIVIAL =
+      new ClassInitializationAnalysis() {
+
+        @Override
+        public boolean isClassDefinitelyLoadedBeforeInstruction(
+            DexType type, Instruction instruction) {
+          return false;
+        }
+      };
+
+  private final AppView<? extends AppInfoWithLiveness> appView;
+  private final IRCode code;
+  private final DexItemFactory dexItemFactory;
+
+  private DominatorTree dominatorTree = null;
+  private int markingColor = -1;
+
+  private ClassInitializationAnalysis() {
+    this.appView = null;
+    this.code = null;
+    this.dexItemFactory = null;
+  }
+
+  public ClassInitializationAnalysis(AppView<? extends AppInfoWithLiveness> appView, IRCode code) {
+    assert appView != null;
+    assert code != null;
+    this.appView = appView;
+    this.code = code;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  // Returns a trivial, conservative analysis that always returns false.
+  public static ClassInitializationAnalysis trivial() {
+    return TRIVIAL;
+  }
+
+  public boolean isClassDefinitelyLoadedBeforeInstruction(DexType type, Instruction instruction) {
+    BasicBlock block = instruction.getBlock();
+
+    // Visit the instructions in `block` prior to `instruction`.
+    for (Instruction previous : block.getInstructions()) {
+      if (previous == instruction) {
+        break;
+      }
+      if (previous.definitelyTriggersClassInitialization(
+          type,
+          appView,
+          Query.DIRECTLY_OR_INDIRECTLY,
+          // The given instruction is only reached if none of the instructions in the same
+          // basic block throws, so we can safely assume that they will not.
+          AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
+        return true;
+      }
+    }
+
+    if (dominatorTree == null) {
+      dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
+    }
+
+    // Visit all the instructions in all the blocks that dominate `block`.
+    for (BasicBlock dominator : dominatorTree.dominatorBlocks(block, Inclusive.NO)) {
+      AnalysisAssumption assumption = getAssumptionForDominator(dominator, block);
+      Iterator<Instruction> instructionIterator = dominator.iterator();
+      while (instructionIterator.hasNext()) {
+        Instruction previous = instructionIterator.next();
+        if (previous.definitelyTriggersClassInitialization(
+            type, appView, Query.DIRECTLY_OR_INDIRECTLY, assumption)) {
+          return true;
+        }
+        if (dominator.hasCatchHandlers() && previous.instructionTypeCanThrow()) {
+          // All of the instructions that follow the first instruction that may throw are
+          // guaranteed to be non-throwing. Hence they cannot cause any class initializations.
+          assert Streams.stream(instructionIterator)
+              .noneMatch(Instruction::instructionTypeCanThrow);
+          break;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns the analysis assumption to use when analyzing the instructions in the given dominator
+   * block.
+   *
+   * <p>If the given block has no catch handlers, then we can safely assume that the instruction
+   * does not throw, because execution would otherwise exit the method.
+   *
+   * <p>As a simple example, consider the method below. In order for the execution to get from the
+   * call to A.foo() to the call to A.bar() the call A.foo() must not throw.
+   *
+   * <pre>
+   *   public static void method() {
+   *     A.foo();
+   *     A.bar();
+   *   }
+   * </pre>
+   *
+   * This assumption cannot be made in the presence of intraprocedural exceptional control flow.
+   * Consider the following example.
+   *
+   * <pre>
+   *   public static void method(A instance) {
+   *     try {
+   *       instance.field = 42;
+   *     } catch (Exception e) {
+   *       A.foo();
+   *       return;
+   *     }
+   *     A.bar();
+   *   }
+   * </pre>
+   *
+   * <p>At the call to A.foo() it is not guaranteed that the class A has been initialized, since
+   * `instance` could always be null.
+   *
+   * <p>At the call to A.bar() it is guaranteed, since the instance field assignment succeeded.
+   */
+  private AnalysisAssumption getAssumptionForDominator(BasicBlock dominator, BasicBlock block) {
+    if (!dominator.hasCatchHandlers()) {
+      return AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW;
+    }
+
+    Instruction exceptionalExit = dominator.exceptionalExit();
+    if (exceptionalExit == null) {
+      // The block cannot throw after all.
+      return AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW;
+    }
+
+    if (markingColor < 0) {
+      markingColor = code.reserveMarkingColor();
+      code.markTransitivePredecessors(block, markingColor);
+    }
+
+    for (CatchHandler<BasicBlock> catchHandler : dominator.getCatchHandlers()) {
+      if (!catchHandler.target.isMarked(markingColor)) {
+        // There is no path from this catch handler to the instruction of interest, so we can
+        // ignore it.
+        continue;
+      }
+
+      DexType guard = catchHandler.guard;
+      if (guard == DexItemFactory.catchAllType) {
+        return AnalysisAssumption.NONE;
+      }
+
+      if (exceptionalExit.isInstanceGet()
+          || exceptionalExit.isInstancePut()
+          || exceptionalExit.isInvokeMethodWithReceiver()) {
+        // If an instance-get, instance-put, or instance-invoke instruction does not fail with a
+        // NullPointerException, then the receiver class must have been initialized.
+        if (!dexItemFactory.npeType.isSubtypeOf(guard, appView.appInfo())) {
+          continue;
+        }
+      }
+      if (exceptionalExit.isStaticGet()
+          || exceptionalExit.isStaticPut()
+          || exceptionalExit.isInvokeStatic()) {
+        // If a static-get, static-put, or invoke-static does not fail with an ExceptionIn-
+        // InitializerError, then the holder class must have been initialized.
+        if (!dexItemFactory.exceptionInInitializerErrorType.isSubtypeOf(guard, appView.appInfo())) {
+          continue;
+        }
+      }
+
+      return AnalysisAssumption.NONE;
+    }
+
+    // There are no paths from any of the catch handlers to the instruction of interest, so we can
+    // assume that no instructions in the given block will throw (otherwise, the instruction of
+    // interest will not be reached).
+    return AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW;
+  }
+
+  /**
+   * The analysis reuses the dominator tree and basic block markings. If the underlying structure of
+   * the IR changes, then this method must be called to reset the dominator tree, return the current
+   * marking color, and clear all marks.
+   */
+  public void notifyCodeHasChanged() {
+    dominatorTree = null;
+    returnMarkingColor();
+  }
+
+  /** Returns the marking color, if any, and clears all marks. */
+  public void finish() {
+    returnMarkingColor();
+  }
+
+  private void returnMarkingColor() {
+    if (markingColor >= 0) {
+      code.returnMarkingColor(markingColor);
+      markingColor = -1;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 3f6a2ef..34926fa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -5,14 +5,16 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiConsumer;
 
-public class CatchHandlers<T> {
+public class CatchHandlers<T> implements Iterable<CatchHandler<T>> {
 
   public static class CatchHandler<T> {
 
@@ -94,6 +96,27 @@
   }
 
   @Override
+  public Iterator<CatchHandler<T>> iterator() {
+    return new Iterator<CatchHandler<T>>() {
+
+      private int nextIndex = 0;
+
+      @Override
+      public boolean hasNext() {
+        return nextIndex < size();
+      }
+
+      @Override
+      public CatchHandler<T> next() {
+        DexType guard = guards.get(nextIndex);
+        T target = targets.get(nextIndex);
+        ++nextIndex;
+        return new CatchHandler<>(guard, target);
+      }
+    };
+  }
+
+  @Override
   public boolean equals(Object o) {
     if (this == o) {
       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 b8192be..960ea92 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
@@ -20,6 +20,11 @@
     MAY_HAVE_UNREACHABLE_BLOCKS
   }
 
+  public enum Inclusive {
+    YES,
+    NO
+  }
+
   private final BasicBlock[] sorted;
   private BasicBlock[] doms;
   private final BasicBlock normalExitBlock = new BasicBlock();
@@ -165,30 +170,42 @@
    * 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 () -> new Iterator<BasicBlock>() {
-      private BasicBlock current = dominated;
+    return () -> {
+      Iterator<BasicBlock> iterator =
+          new Iterator<BasicBlock>() {
+            private BasicBlock current = dominated;
 
-      @Override
-      public boolean hasNext() {
-        return current != null;
-      }
+            @Override
+            public boolean hasNext() {
+              return current != null;
+            }
 
-      @Override
-      public BasicBlock next() {
-        if (!hasNext()) {
-          return null;
-        } else {
-          BasicBlock result = current;
-          if (current.getNumber() == 0) {
-            current = null;
-          } else {
-            current = immediateDominator(current);
-            assert current != result;
-          }
-          return result;
-        }
+            @Override
+            public BasicBlock next() {
+              if (!hasNext()) {
+                return null;
+              } else {
+                BasicBlock result = current;
+                if (current.getNumber() == 0) {
+                  current = null;
+                } else {
+                  current = immediateDominator(current);
+                  assert current != result;
+                }
+                return result;
+              }
+            }
+          };
+      if (inclusive == Inclusive.NO) {
+        BasicBlock block = iterator.next();
+        assert block == dominated;
       }
+      return iterator;
     };
   }
 
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 a5070ca..870ae6f 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
@@ -387,11 +387,9 @@
     }
   }
 
-  private boolean verifyNoBlocksMarked(int color) {
+  public boolean verifyNoBlocksMarked(int color) {
     for (BasicBlock block : blocks) {
-      if (block.isMarked(color)) {
-        return false;
-      }
+      assert !block.isMarked(color);
     }
     return true;
   }
@@ -667,15 +665,8 @@
           }
           // After the throwing instruction only debug instructions and the final jump
           // instruction is allowed.
-          // TODO(mkroghj) Temporarily allow stack-operations to be after throwing instructions.
           if (seenThrowing) {
-            assert instruction.isDebugInstruction()
-                || instruction.isJumpInstruction()
-                || instruction.isStore()
-                || instruction.isPop()
-                || instruction.isDup()
-                || instruction.isDup2()
-                || instruction.isSwap();
+            assert instruction.isDebugInstruction() || instruction.isGoto();
           }
         }
       }
@@ -919,7 +910,7 @@
   public Set<BasicBlock> getUnreachableBlocks() {
     Set<BasicBlock> unreachableBlocks = Sets.newIdentityHashSet();
     int color = reserveMarkingColor();
-    markReachableBlocks(color);
+    markTransitiveSuccessors(blocks.getFirst(), color);
     for (BasicBlock block : blocks) {
       if (!block.isMarked(color)) {
         unreachableBlocks.add(block);
@@ -932,7 +923,7 @@
   public Set<Value> removeUnreachableBlocks() {
     ImmutableSet.Builder<Value> affectedValueBuilder = ImmutableSet.builder();
     int color = reserveMarkingColor();
-    markReachableBlocks(color);
+    markTransitiveSuccessors(blocks.getFirst(), color);
     ListIterator<BasicBlock> blockIterator = listIterator();
     while (blockIterator.hasNext()) {
       BasicBlock current = blockIterator.next();
@@ -946,10 +937,10 @@
   }
 
   // Note: It is the responsibility of the caller to return the marking color.
-  private void markReachableBlocks(int color) {
+  private void markTransitiveSuccessors(BasicBlock subject, int color) {
     assert isMarkingColorInUse(color) && !anyBlocksMarkedWithColor(color);
     Queue<BasicBlock> worklist = new ArrayDeque<>();
-    worklist.add(blocks.getFirst());
+    worklist.add(subject);
     while (!worklist.isEmpty()) {
       BasicBlock block = worklist.poll();
       if (block.isMarked(color)) {
@@ -963,4 +954,27 @@
       }
     }
   }
+
+  /**
+   * Marks the transitive predecessors of the given block, including the block itself.
+   *
+   * <p>Note: It is the responsibility of the caller to return the marking color.
+   */
+  public void markTransitivePredecessors(BasicBlock subject, int color) {
+    assert isMarkingColorInUse(color) && !anyBlocksMarkedWithColor(color);
+    Queue<BasicBlock> worklist = new ArrayDeque<>();
+    worklist.add(subject);
+    while (!worklist.isEmpty()) {
+      BasicBlock block = worklist.poll();
+      if (block.isMarked(color)) {
+        continue;
+      }
+      block.mark(color);
+      for (BasicBlock predecessor : block.getPredecessors()) {
+        if (!predecessor.isMarked(color)) {
+          worklist.add(predecessor);
+        }
+      }
+    }
+  }
 }
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 d292aca..acc122b 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
@@ -17,9 +17,13 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 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.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+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.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -158,4 +162,24 @@
   public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
     return object() == value;
   }
+
+  @Override
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      if (object().getTypeLattice().isNullable()) {
+        // If the receiver is null we cannot be sure that the holder has been initialized.
+        return false;
+      }
+    }
+    DexType holder = getField().clazz;
+    if (mode == Query.DIRECTLY) {
+      return holder == clazz;
+    } else {
+      return holder.isSubtypeOf(clazz, appView.appInfo());
+    }
+  }
 }
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 2d70317..48bc0c0 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
@@ -15,9 +15,13 @@
 import com.android.tools.r8.code.IputWide;
 import com.android.tools.r8.dex.Constants;
 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.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -141,4 +145,24 @@
   public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
     return object() == value;
   }
+
+  @Override
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      if (object().getTypeLattice().isNullable()) {
+        // If the receiver is null we cannot be sure that the holder has been initialized.
+        return false;
+      }
+    }
+    DexType holder = getField().clazz;
+    if (mode == Query.DIRECTLY) {
+      return holder == clazz;
+    } else {
+      return holder.isSubtypeOf(clazz, appView.appInfo());
+    }
+  }
 }
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 6d21c5a..d8500e6 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
@@ -8,10 +8,14 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 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.DebugLocalInfo;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+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.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstRangeLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
@@ -1250,11 +1254,14 @@
    * Indicates whether the instruction triggers the class initialization (i.e. the <clinit> method)
    * of the given class at runtime execution.
    *
-   * @param klass a class of the program
    * @return true if the instruction triggers intialization of the class at runtime, false
-   * otherwise.
+   *     otherwise.
    */
-  public boolean triggersInitializationOfClass(DexType klass) {
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
     return false;
   }
 
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 10a80e2..ca1180f 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
@@ -7,9 +7,12 @@
 import com.android.tools.r8.code.InvokeDirectRange;
 import com.android.tools.r8.dex.Constants;
 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.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -120,4 +123,24 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod(), itf));
   }
+
+  @Override
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      if (getReceiver().getTypeLattice().isNullable()) {
+        // If the receiver is null we cannot be sure that the holder has been initialized.
+        return false;
+      }
+    }
+    DexType holder = getInvokedMethod().holder;
+    if (mode == Query.DIRECTLY) {
+      return holder == clazz;
+    } else {
+      return holder.isSubtypeOf(clazz, appView.appInfo());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 060f1d8..d203184 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -9,6 +9,7 @@
 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.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -61,7 +62,10 @@
   public abstract Collection<DexEncodedMethod> lookupTargets(
       AppInfoWithSubtyping appInfo, DexType invocationContext);
 
-  public abstract InlineAction computeInlining(InliningOracle decider, DexType invocationContext);
+  public abstract InlineAction computeInlining(
+      InliningOracle decider,
+      DexType invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis);
 
   @Override
   public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 44ec246..f994058 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -34,7 +35,10 @@
   }
 
   @Override
-  public final InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+  public final InlineAction computeInlining(
+      InliningOracle decider,
+      DexType invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis) {
     return decider.computeForInvokeWithReceiver(this, invocationContext);
   }
 
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 fa760ff..4b9ff7d 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
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -126,7 +127,10 @@
   }
 
   @Override
-  public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+  public InlineAction computeInlining(
+      InliningOracle decider,
+      DexType invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis) {
     return decider.computeForInvokePolymorphic(this, invocationContext);
   }
 }
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 d5f86fa..e203cc6 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
@@ -6,9 +6,13 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeStaticRange;
 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.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -105,8 +109,11 @@
   }
 
   @Override
-  public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
-    return decider.computeForInvokeStatic(this, invocationContext);
+  public InlineAction computeInlining(
+      InliningOracle decider,
+      DexType invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis) {
+    return decider.computeForInvokeStatic(this, invocationContext, classInitializationAnalysis);
   }
 
   @Override
@@ -115,7 +122,20 @@
   }
 
   @Override
-  public boolean triggersInitializationOfClass(DexType klass) {
-    return getInvokedMethod().holder == klass;
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      // Class initialization may fail with ExceptionInInitializerError.
+      return false;
+    }
+    DexType holder = getInvokedMethod().holder;
+    if (mode == Query.DIRECTLY) {
+      return holder == clazz;
+    } else {
+      return holder.isSubtypeOf(clazz, appView.appInfo());
+    }
   }
 }
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 cbb645f..b02ca94 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
@@ -5,10 +5,15 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 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.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -107,4 +112,39 @@
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeSuper(getInvokedMethod(), invocationContext);
   }
+
+  @Override
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      if (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;
+    }
+    DexMethod method = getInvokedMethod();
+    DexClass enclosingClass = appView.appInfo().definitionFor(method.holder);
+    if (enclosingClass == null) {
+      return false;
+    }
+    DexType superType = enclosingClass.superType;
+    if (superType == null) {
+      return false;
+    }
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(superType, method);
+    if (!resolutionResult.hasSingleTarget()) {
+      return false;
+    }
+    DexType holder = resolutionResult.asSingleTarget().method.holder;
+    return holder.isSubtypeOf(clazz, appView.appInfo());
+  }
 }
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 7ef8979..4c2327f 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
@@ -5,10 +5,14 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 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.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;
@@ -98,4 +102,31 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, getInvokedMethod(), false));
   }
+
+  @Override
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      if (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;
+    }
+    DexMethod method = getInvokedMethod();
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    if (!resolutionResult.hasSingleTarget()) {
+      return false;
+    }
+    DexType holder = resolutionResult.asSingleTarget().method.holder;
+    return holder.isSubtypeOf(clazz, appView.appInfo());
+  }
 }
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 fca7b7e..93b1042 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
@@ -8,7 +8,11 @@
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.dex.Constants;
 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.DexType;
+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.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -107,8 +111,17 @@
   }
 
   @Override
-  public boolean triggersInitializationOfClass(DexType klass) {
-    return clazz == klass;
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    DexType holder = this.clazz;
+    if (mode == Query.DIRECTLY) {
+      return holder == clazz;
+    } else {
+      return holder.isSubtypeOf(clazz, appView.appInfo());
+    }
   }
 
   public void markNoSpilling() {
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 af9c5c6..db3838c 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
@@ -16,8 +16,12 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 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.DexField;
 import com.android.tools.r8.graph.DexType;
+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.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -147,7 +151,20 @@
   }
 
   @Override
-  public boolean triggersInitializationOfClass(DexType klass) {
-    return getField().clazz == klass;
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      // Class initialization may fail with ExceptionInInitializerError.
+      return false;
+    }
+    DexType holder = getField().clazz;
+    if (mode == Query.DIRECTLY) {
+      return holder == clazz;
+    } else {
+      return holder.isSubtypeOf(clazz, appView.appInfo());
+    }
   }
 }
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 ba33cbf..4f63a1b 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
@@ -14,8 +14,12 @@
 import com.android.tools.r8.code.SputWide;
 import com.android.tools.r8.dex.Constants;
 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.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -130,7 +134,20 @@
   }
 
   @Override
-  public boolean triggersInitializationOfClass(DexType klass) {
-    return getField().clazz == klass;
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    if (assumption == AnalysisAssumption.NONE) {
+      // Class initialization may fail with ExceptionInInitializerError.
+      return false;
+    }
+    DexType holder = getField().clazz;
+    if (mode == Query.DIRECTLY) {
+      return holder == clazz;
+    } else {
+      return holder.isSubtypeOf(clazz, appView.appInfo());
+    }
   }
 }
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 21cb146..202bac6 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
@@ -75,7 +75,7 @@
 
   @Override
   public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    // Nothing to do. This is only hit because loads and stores are insert for phis.
+    throw new Unreachable("This IR must not be inserted before load and store insertion.");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 6b5f6c4..8a46e1e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -149,9 +149,6 @@
     computeInitializers();
     typeVerificationHelper = new TypeVerificationHelper(code, factory, appInfo);
     typeVerificationHelper.computeVerificationTypes();
-    if (!options.testing.noSplittingExceptionalEdges) {
-      splitExceptionalBlocks();
-    }
     rewriter.converter.deadCodeRemover.run(code);
     rewriteNots();
     LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, typeVerificationHelper, appInfo);
@@ -223,38 +220,6 @@
     return initializers;
   }
 
-  // Split all blocks with throwing instructions and exceptional edges such that any non-throwing
-  // instructions that might define values prior to the throwing exception are excluded from the
-  // try-catch range. Failure to do so will result in code that does not verify on the JVM.
-  private void splitExceptionalBlocks() {
-    ListIterator<BasicBlock> it = code.listIterator();
-    while (it.hasNext()) {
-      BasicBlock block = it.next();
-      if (!block.hasCatchHandlers()) {
-        continue;
-      }
-      int size = block.getInstructions().size();
-      boolean isThrow = block.exit().isThrow();
-      if ((isThrow && size == 1) || (!isThrow && size == 2)) {
-        // Fast-path to avoid processing blocks with just a single throwing instruction.
-        continue;
-      }
-      InstructionListIterator instructions = block.listIterator();
-      boolean hasOutValues = false;
-      while (instructions.hasNext()) {
-        Instruction instruction = instructions.next();
-        if (instruction.instructionTypeCanThrow()) {
-          break;
-        }
-        hasOutValues |= instruction.outValue() != null;
-      }
-      if (hasOutValues) {
-        instructions.previous();
-        instructions.split(code, it);
-      }
-    }
-  }
-
   private void rewriteNots() {
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator();
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 5bf9154..0b5e17e 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
@@ -1125,12 +1125,14 @@
 
     codeRewriter.identifyReturnsArgument(method, code, feedback);
     if (options.enableInlining && inliner != null) {
-      codeRewriter.identifyInvokeSemanticsForInlining(method, code, graphLense(), feedback);
+      codeRewriter.identifyInvokeSemanticsForInlining(method, code, appView, feedback);
     }
 
     // Track usage of parameters and compute their nullability and possibility of NPE.
-    if (method.getOptimizationInfo().getNonNullParamOrThrow() == null) {
-      computeNonNullParamHints(feedback, method, code);
+    if (enableWholeProgramOptimizations) {
+      if (method.getOptimizationInfo().getNonNullParamOrThrow() == null) {
+        computeNonNullParamHints(feedback, method, code);
+      }
     }
 
     // Insert code to log arguments if requested.
@@ -1189,8 +1191,7 @@
       //   invoke-static throwParameterIsNullException(msg)
       //
       // or some other variants, e.g., throw null or NPE after the direct null check.
-      if (argument.isUsed()
-          && checksNullBeforeSideEffect(code, appInfo, graphLense(), argument)) {
+      if (argument.isUsed() && checksNullBeforeSideEffect(code, argument, appView)) {
         paramsCheckedForNull.set(index);
       }
     }
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 0af7c9e..359223d 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query.DIRECTLY;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.CANONICAL_NAME;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 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.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -42,10 +44,10 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
@@ -1024,17 +1026,19 @@
   }
 
   public void identifyInvokeSemanticsForInlining(
-      DexEncodedMethod method, IRCode code, GraphLense graphLense, OptimizationFeedback feedback) {
+      DexEncodedMethod method,
+      IRCode code,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      OptimizationFeedback feedback) {
     if (method.isStatic()) {
       // Identifies if the method preserves class initialization after inlining.
-      feedback.markTriggerClassInitBeforeAnySideEffect(method,
-          triggersClassInitializationBeforeSideEffect(code, method.method.getHolder()));
+      feedback.markTriggerClassInitBeforeAnySideEffect(
+          method, triggersClassInitializationBeforeSideEffect(method.method.holder, code, appView));
     } else {
       // Identifies if the method preserves null check of the receiver after inlining.
       final Value receiver = code.getThis();
-      feedback.markCheckNullReceiverBeforeAnySideEffect(method,
-          receiver.isUsed()
-              && checksNullBeforeSideEffect(code, appInfo, graphLense, receiver));
+      feedback.markCheckNullReceiverBeforeAnySideEffect(
+          method, receiver.isUsed() && checksNullBeforeSideEffect(code, receiver, appView));
     }
   }
 
@@ -1366,10 +1370,10 @@
    * Returns true if the given code unconditionally throws if value is null before any other side
    * effect instruction.
    *
-   * Note: we do not track phis so we may return false negative. This is a conservative approach.
+   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
    */
   public static boolean checksNullBeforeSideEffect(
-      IRCode code, AppInfo appInfo, GraphLense graphLense, Value value) {
+      IRCode code, Value value, AppView<? extends AppInfoWithSubtyping> appView) {
     return alwaysTriggerExpectedEffectBeforeAnythingElse(
         code,
         (instr, it) -> {
@@ -1379,13 +1383,13 @@
           if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
             return InstructionEffect.CONDITIONAL_EFFECT;
           }
-          if (isKotlinNullCheck(appInfo, graphLense, instr, value)) {
+          if (isKotlinNullCheck(instr, value, appView)) {
             DexMethod invokedMethod = instr.asInvokeStatic().getInvokedMethod();
             // Kotlin specific way of throwing NPE: throwParameterIsNullException.
             // Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
             // the value.
             if (invokedMethod.name
-                == appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException.name) {
+                == appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException.name) {
               // We found a NPE (or similar exception) throwing code.
               // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on the value.
               for (BasicBlock predecessor : currentBlock.getPredecessors()) {
@@ -1401,14 +1405,14 @@
             } else {
               // Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
               assert invokedMethod.name
-                  == appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull.name;
+                  == appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull.name;
               return InstructionEffect.DESIRED_EFFECT;
             }
           }
-          if (isInstantiationOfNullPointerException(instr, it, appInfo.dexItemFactory)) {
+          if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
             it.next(); // Skip call to NullPointerException.<init>.
             return InstructionEffect.NO_EFFECT;
-          } else if (instr.throwsNpeIfValueIsNull(value, appInfo.dexItemFactory)) {
+          } else if (instr.throwsNpeIfValueIsNull(value, appView.dexItemFactory())) {
             // In order to preserve NPE semantic, the exception must not be caught by any handler.
             // Therefore, we must ignore this instruction if it is covered by a catch handler.
             // Note: this is a conservative approach where we consider that any catch handler could
@@ -1434,7 +1438,7 @@
   // declare a method called checkParameterIsNotNull(parameter, message) or
   // throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
   private static boolean isKotlinNullCheck(
-      AppInfo appInfo, GraphLense graphLense, Instruction instr, Value value) {
+      Instruction instr, Value value, AppView<? extends AppInfoWithSubtyping> appView) {
     if (!instr.isInvokeStatic()) {
       return false;
     }
@@ -1442,11 +1446,11 @@
     // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
     MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
     Wrapper<DexMethod> checkParameterIsNotNull =
-        wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull);
+        wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull);
     Wrapper<DexMethod> throwParamIsNullException =
-        wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException);
+        wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException);
     DexMethod invokedMethod =
-        graphLense.getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
+        appView.graphLense().getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
     Wrapper<DexMethod> methodWrap = wrapper.wrap(invokedMethod);
     if (methodWrap.equals(throwParamIsNullException)
         || (methodWrap.equals(checkParameterIsNotNull) && instr.inValues().get(0).equals(value))) {
@@ -1492,13 +1496,15 @@
    * Returns true if the given code unconditionally triggers class initialization before any other
    * side effecting instruction.
    *
-   * Note: we do not track phis so we may return false negative. This is a conservative approach.
+   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
    */
-  private static boolean triggersClassInitializationBeforeSideEffect(IRCode code, DexType klass) {
+  private static boolean triggersClassInitializationBeforeSideEffect(
+      DexType clazz, IRCode code, AppView<? extends AppInfoWithSubtyping> appView) {
     return alwaysTriggerExpectedEffectBeforeAnythingElse(
         code,
         (instruction, it) -> {
-          if (instruction.triggersInitializationOfClass(klass)) {
+          if (instruction.definitelyTriggersClassInitialization(
+              clazz, 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/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 357e949..d7faa6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -115,14 +116,20 @@
     return Reason.SIMPLE;
   }
 
-  private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
+  private boolean canInlineStaticInvoke(
+      InvokeStatic invoke,
+      DexEncodedMethod method,
+      DexEncodedMethod target,
+      ClassInitializationAnalysis classInitializationAnalysis) {
     // Only proceed with inlining a static invoke if:
-    // - the holder for the target equals the holder for the method, or
+    // - the holder for the target is a subtype of the holder for the method,
     // - the target method always triggers class initialization of its holder before any other side
-    //   effect (hence preserving class initialization semantics).
+    //   effect (hence preserving class initialization semantics),
+    // - the current method has already triggered the holder for the target method to be
+    //   initialized, or
     // - there is no non-trivial class initializer.
-    DexType targetHolder = target.method.getHolder();
-    if (method.method.getHolder() == targetHolder) {
+    DexType targetHolder = target.method.holder;
+    if (method.method.holder.isSubtypeOf(targetHolder, appView.appInfo())) {
       return true;
     }
     DexClass clazz = inliner.appView.appInfo().definitionFor(targetHolder);
@@ -130,6 +137,10 @@
     if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
       return true;
     }
+    if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(
+        target.method.holder, invoke)) {
+      return true;
+    }
     // Check for class initializer side effects when loading this class, as inlining might remove
     // the load operation.
     //
@@ -316,7 +327,10 @@
   }
 
   @Override
-  public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+  public InlineAction computeForInvokeStatic(
+      InvokeStatic invoke,
+      DexType invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis) {
     DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
     if (candidate == null || inliner.isBlackListed(candidate.method)) {
       return null;
@@ -333,7 +347,7 @@
     }
 
     // Abort inlining attempt if we can not guarantee class for static target has been initialized.
-    if (!canInlineStaticInvoke(method, candidate)) {
+    if (!canInlineStaticInvoke(invoke, method, candidate, classInitializationAnalysis)) {
       if (info != null) {
         info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 6890dd8..5ffa2cf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -39,7 +40,10 @@
   }
 
   @Override
-  public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+  public InlineAction computeForInvokeStatic(
+      InvokeStatic invoke,
+      DexType invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis) {
     return computeForInvoke(invoke);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 27ea742..953562f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexField;
 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.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -593,6 +594,8 @@
       InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod context, IRCode code) {
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
+    ClassInitializationAnalysis classInitializationAnalysis =
+        new ClassInitializationAnalysis(appView, code);
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (blocksToRemove.contains(block)) {
@@ -603,7 +606,8 @@
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
-          InlineAction result = invoke.computeInlining(oracle, context.method.holder);
+          InlineAction result =
+              invoke.computeInlining(oracle, context.method.holder, classInitializationAnalysis);
           if (result != null) {
             if (!(strategy.stillHasBudget() || result.reason.mustBeInlined())) {
               continue;
@@ -641,6 +645,8 @@
               strategy.markInlined(inlinee);
               iterator.inlineInvoke(
                   appView.appInfo(), code, inlinee.code, blockIterator, blocksToRemove, downcast);
+
+              classInitializationAnalysis.notifyCodeHasChanged();
               strategy.updateTypeInformationIfNeeded(inlinee.code, blockIterator, block);
 
               // If we inlined the invoke from a bridge method, it is no longer a bridge method.
@@ -656,6 +662,7 @@
         }
       }
     }
+    classInitializationAnalysis.finish();
     oracle.finish();
     code.removeBlocks(blocksToRemove);
     code.removeAllTrivialPhis();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 08f8045..15a6329 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -21,7 +22,9 @@
       InvokeMethodWithReceiver invoke, DexType invocationContext);
 
   InlineAction computeForInvokeStatic(
-      InvokeStatic invoke, DexType invocationContext);
+      InvokeStatic invoke,
+      DexType invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis);
 
   InlineAction computeForInvokePolymorphic(
       InvokePolymorphic invoke, DexType invocationContext);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index cdf37c4..1f036b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.OptimizationInfo;
 import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -822,7 +823,9 @@
       }
 
       // Check if the method is inline-able by standard inliner.
-      InlineAction inlineAction = invoke.computeInlining(defaultOracle.get(), method.method.holder);
+      InlineAction inlineAction =
+          invoke.computeInlining(
+              defaultOracle.get(), method.method.holder, ClassInitializationAnalysis.trivial());
       if (inlineAction == null) {
         return false;
       }
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 3997982..7f4fbe1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -539,7 +539,6 @@
     public boolean forceNameReflectionOptimization = false;
     public boolean disallowLoadStoreOptimization = false;
     public Consumer<IRCode> irModifier = null;
-    public boolean noSplittingExceptionalEdges = false;
   }
 
   private boolean hasMinApi(AndroidApiLevel level) {
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index e4c7098..926a22d 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -8,9 +8,19 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
 import java.util.ListIterator;
 import org.junit.Test;
 
@@ -18,8 +28,7 @@
  * This tests that we produce valid code when having normal-flow with exceptional edges in blocks.
  * We might perform optimizations that add operations (dup, swap, etc.) before and after
  * instructions that lie on the boundary of the exception table that is generated for a basic block.
- * If live-ranges are minimized this could produce VerifyErrors. TODO(b/119771771) Will fail if
- * shorten live ranges without shorten exception table range.
+ * If live-ranges are minimized this could produce VerifyErrors.
  */
 public class TryRangeTestRunner extends TestBase {
 
@@ -37,27 +46,45 @@
               o.testing.disallowLoadStoreOptimization = true;
             })
         .run(TryRangeTest.class)
-        .assertSuccess();
+        .assertSuccessWithOutput(StringUtils.lines("10", "7.0"));
   }
 
   @Test
   public void testRegisterAllocationLimitLeadingRange() throws Exception {
-    testForR8(Backend.CF)
-        .addProgramClasses(TryRangeTestLimitRange.class)
-        .addKeepMainRule(TryRangeTestLimitRange.class)
-        .setMode(CompilationMode.RELEASE)
-        .minification(false)
-        .noTreeShaking()
-        .enableInliningAnnotations()
-        .addOptionsModification(
-            o -> {
-              o.testing.disallowLoadStoreOptimization = true;
-              o.testing.irModifier = this::processIR;
-              // TODO(mkroghj) Remove this option entirely when splittingExceptionalEdges is moved.
-              o.testing.noSplittingExceptionalEdges = true;
-            })
-        .run(TryRangeTestLimitRange.class)
-        .assertFailure();
+    CodeInspector inspector =
+        testForR8(Backend.CF)
+            .addProgramClasses(TryRangeTestLimitRange.class)
+            .addKeepMainRule(TryRangeTestLimitRange.class)
+            .setMode(CompilationMode.RELEASE)
+            .minification(false)
+            .noTreeShaking()
+            .enableInliningAnnotations()
+            .addOptionsModification(
+                o -> {
+                  o.testing.disallowLoadStoreOptimization = true;
+                  o.testing.irModifier = this::processIR;
+                })
+            .run(TryRangeTestLimitRange.class)
+            .assertSuccessWithOutput("")
+            .inspector();
+    // Assert that we do not have any register-modifying instructions in the throwing block:
+    // L0: ; locals:
+    // iload 1;
+    // invokestatic com.android.tools.r8.cf.TryRangeTestLimitRange.doSomething(I)F
+    // L1: ; locals:
+    // 11:   pop
+    ClassSubject clazz = inspector.clazz("com.android.tools.r8.cf.TryRangeTestLimitRange");
+    CfCode cfCode = clazz.uniqueMethodWithName("main").getMethod().getCode().asCfCode();
+    List<CfInstruction> instructions = cfCode.getInstructions();
+    CfLabel startLabel = cfCode.getTryCatchRanges().get(0).start;
+    int index = 0;
+    while (instructions.get(index) != startLabel) {
+      index++;
+    }
+    assert instructions.get(index + 1) instanceof CfLoad;
+    assert instructions.get(index + 2) instanceof CfInvoke;
+    assert instructions.get(index + 3) == cfCode.getTryCatchRanges().get(0).end;
+    assert instructions.get(index + 4) instanceof CfStackInstruction;
   }
 
   private void processIR(IRCode code) {
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index da0bc9e..62c6bc8 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -17,8 +17,6 @@
   private String proguardMap1 = null;
   private String proguardMap2 = null;
 
-  @Rule public final ExpectedException exception = ExpectedException.none();
-
   @Test
   public void buildAndTreeShakeFromDeployJar() throws Exception {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
new file mode 100644
index 0000000..1144491
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
@@ -0,0 +1,373 @@
+// 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.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+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.MethodSubject;
+import org.junit.Test;
+
+public class InliningAfterClassInitializationTest extends TestBase {
+
+  @Test
+  public void testClass1() throws Exception {
+    Class<TestClass1> mainClass = TestClass1.class;
+    CodeInspector inspector =
+        buildAndRun(
+            mainClass,
+            StringUtils.lines("In A.<clinit>()", "In A.notInlineable()", "In A.inlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+
+    MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+    assertThat(notInlineableMethod, isPresent());
+
+    MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
+    assertThat(testMethod, isPresent());
+    assertThat(testMethod, invokesMethod(notInlineableMethod));
+  }
+
+  @Test
+  public void testClass2() throws Exception {
+    Class<TestClass2> mainClass = TestClass2.class;
+    CodeInspector inspector =
+        buildAndRun(
+            mainClass,
+            StringUtils.lines("In A.<clinit>()", "Field A.staticField", "In A.inlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+  }
+
+  @Test
+  public void testClass3() throws Exception {
+    Class<TestClass3> mainClass = TestClass3.class;
+    CodeInspector inspector =
+        buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+  }
+
+  @Test
+  public void testClass4() throws Exception {
+    Class<TestClass4> mainClass = TestClass4.class;
+    CodeInspector inspector =
+        buildAndRun(
+            mainClass,
+            StringUtils.lines("In A.<clinit>()", "Field A.instanceField", "In A.inlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+  }
+
+  @Test
+  public void testClass5() throws Exception {
+    Class<TestClass5> mainClass = TestClass5.class;
+    CodeInspector inspector =
+        buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+  }
+
+  @Test
+  public void testClass6() throws Exception {
+    Class<TestClass6> mainClass = TestClass6.class;
+    CodeInspector inspector =
+        buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.notInlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+    assertThat(notInlineableMethod, isPresent());
+
+    MethodSubject testMethod = inspector.clazz(mainClass).uniqueMethodWithName("test");
+    assertThat(testMethod, isPresent());
+    assertThat(testMethod, invokesMethod(notInlineableMethod));
+  }
+
+  @Test
+  public void testClass7() throws Exception {
+    Class<TestClass7> mainClass = TestClass7.class;
+    CodeInspector inspector =
+        buildAndRun(
+            mainClass,
+            StringUtils.lines(
+                "Caught NullPointerException",
+                "In A.<clinit>()",
+                "Field A.instanceField",
+                "In A.inlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+  }
+
+  @Test
+  public void testClass8() throws Exception {
+    Class<TestClass8> mainClass = TestClass8.class;
+    CodeInspector inspector =
+        buildAndRun(
+            mainClass,
+            StringUtils.lines(
+                "In A.<clinit>()", "In A.notInlineable()", "In A.alsoNotInlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+    assertThat(notInlineableMethod, isPresent());
+
+    MethodSubject alsoNotInlineableMethod = classA.uniqueMethodWithName("alsoNotInlineable");
+    assertThat(alsoNotInlineableMethod, isPresent());
+
+    MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
+    assertThat(testMethod, isPresent());
+    assertThat(testMethod, invokesMethod(notInlineableMethod));
+    assertThat(testMethod, invokesMethod(alsoNotInlineableMethod));
+  }
+
+  @Test
+  public void testClass9() throws Exception {
+    Class<TestClass9> mainClass = TestClass9.class;
+    CodeInspector inspector =
+        buildAndRun(
+            mainClass,
+            StringUtils.lines("In A.<clinit>()", "Field A.staticField", "In A.notInlineable()"));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+    assertThat(notInlineableMethod, isPresent());
+
+    MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
+    assertThat(testMethod, isPresent());
+    assertThat(testMethod, invokesMethod(notInlineableMethod));
+  }
+
+  private CodeInspector buildAndRun(Class<?> mainClass, String expectedOutput) throws Exception {
+    testForJvm().addTestClasspath().run(mainClass).assertSuccessWithOutput(expectedOutput);
+
+    return testForR8(Backend.DEX)
+        .addInnerClasses(InliningAfterClassInitializationTest.class)
+        .addKeepMainRule(mainClass)
+        .enableConstantArgumentAnnotations()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .run(mainClass)
+        .assertSuccessWithOutput(expectedOutput)
+        .inspector();
+  }
+
+  static class TestClass1 {
+
+    public static void main(String[] args) {
+      A.notInlineable();
+
+      // Since the previous call will cause the initializer of A to run, we can safely inline this
+      // call.
+      A.inlineable();
+    }
+  }
+
+  static class TestClass2 {
+
+    public static void main(String[] args) {
+      System.out.println(A.staticField);
+
+      // Since the previous instruction will cause the initializer of A to run, we can safely inline
+      // this call.
+      A.inlineable();
+    }
+  }
+
+  static class TestClass3 {
+
+    public static void main(String[] args) {
+      A.staticField = "Hello world!";
+
+      // Since the previous instruction will cause the initializer of A to run, we can safely inline
+      // this call.
+      A.inlineable();
+
+      // Make sure the field is read to prevent the static-put instruction from being removed
+      // (b/123553485).
+      spuriousFieldRead();
+    }
+
+    private static void spuriousFieldRead() {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(A.staticField);
+      }
+    }
+  }
+
+  static class TestClass4 {
+
+    public static void main(String[] args) {
+      test(new A());
+    }
+
+    @NeverInline
+    private static void test(A obj) {
+      System.out.println(obj.instanceField);
+
+      // Since the previous instruction will cause the initializer of A to run, we can safely inline
+      // this call.
+      A.inlineable();
+    }
+  }
+
+  static class TestClass5 {
+
+    public static void main(String[] args) {
+      test(new A());
+    }
+
+    @NeverInline
+    private static void test(A obj) {
+      obj.instanceField = "Hello world!";
+
+      // Since the previous instruction will cause the initializer of A to run, we can safely inline
+      // this call.
+      A.inlineable();
+
+      // Make sure the field is read to prevent the instance-put instruction from being removed.
+      spuriousFieldRead(obj);
+    }
+
+    private static void spuriousFieldRead(A obj) {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(obj.instanceField);
+      }
+    }
+  }
+
+  static class TestClass6 {
+
+    public static void main(String[] args) {
+      test(null);
+    }
+
+    @KeepConstantArguments
+    @NeverInline
+    private static void test(A obj) {
+      try {
+        String value = obj.instanceField;
+        System.out.println(value);
+      } catch (NullPointerException e) {
+        // Ignore.
+      }
+
+      // This call cannot be inlined because `obj.field` may throw a NullPointerException, in which
+      // case A is not guaranteed to be initialized.
+      A.notInlineable();
+    }
+  }
+
+  static class TestClass7 {
+
+    public static void main(String[] args) {
+      test(null);
+      test(new A());
+    }
+
+    @NeverInline
+    private static void test(A obj) {
+      try {
+        String value = obj.instanceField;
+        System.out.println(value);
+      } catch (NullPointerException e) {
+        // Ignore.
+        System.out.println("Caught NullPointerException");
+        return;
+      }
+
+      // Due to the `return` in the catch handler above, A is guaranteed to be initialized if we
+      // reach this line.
+      A.inlineable();
+    }
+  }
+
+  static class TestClass8 {
+
+    public static void main(String[] args) {
+      try {
+        A.notInlineable();
+      } catch (ExceptionInInitializerError e) {
+        System.out.println("Caught ExceptionInInitializerError");
+      }
+
+      A.alsoNotInlineable();
+    }
+  }
+
+  static class TestClass9 {
+
+    public static void main(String[] args) {
+      try {
+        String value = A.staticField;
+        System.out.println(value);
+      } catch (ExceptionInInitializerError e) {
+        System.out.println("Caught ExceptionInInitializerError");
+      }
+
+      A.notInlineable();
+    }
+  }
+
+  static class A {
+
+    public String instanceField = "Field A.instanceField";
+    public static String staticField =
+        System.currentTimeMillis() >= 0 ? "Field A.staticField" : null;
+
+    static {
+      System.out.println("In A.<clinit>()");
+    }
+
+    static void notInlineable() {
+      System.out.println("In A.notInlineable()");
+    }
+
+    static void alsoNotInlineable() {
+      System.out.println("In A.alsoNotInlineable()");
+    }
+
+    static void inlineable() {
+      System.out.println("In A.inlineable()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
index 71c5fbe..d581291 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -16,12 +16,10 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class InliningFromCurrentClassTest extends TestBase {
 
-  @Ignore("b/123327416")
   @Test
   public void test() throws Exception {
     String expectedOutput =
@@ -65,8 +63,6 @@
 
     MethodSubject testMethod = classB.uniqueMethodWithName("test");
     assertThat(testMethod, isPresent());
-    assertThat(testMethod, not(invokesMethod(inlineable1Method)));
-    assertThat(testMethod, not(invokesMethod(inlineable2Method)));
     assertThat(testMethod, invokesMethod(notInlineableMethod));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
index 9691149..a264d15 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
@@ -16,16 +16,13 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class InliningWithClassInitializerTest extends TestBase {
 
-  @Ignore("b/123327413")
   @Test
   public void test() throws Exception {
-    String expectedOutput =
-        StringUtils.lines("In A.<clinit>()", "In B.inlineable()", "In B.other()");
+    String expectedOutput = StringUtils.lines("In A.<clinit>()", "In B.<clinit>()", "In B.other()");
 
     testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
 
@@ -74,8 +71,11 @@
 
   static class B extends A {
 
+    static {
+      System.out.println("In B.<clinit>()");
+    }
+
     static void inlineable() {
-      System.out.println("In B.inlineable()");
       other();
     }
 
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 10fb29f..5f224a6 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -126,6 +126,9 @@
     for line in lines:
       if '-printconfiguration' not in line:
         f.write(line)
+    # Check that there is a line-break at the end of the file or insert one.
+    if lines[-1].strip():
+      f.write('\n')
     f.write('-printconfiguration {}\n'.format(destination))
 
 def FindProguardConfigurationFile(app, config, checkout_dir):