Allow inlining of methods that become simple after inlining

Bug: 132600418
Change-Id: Ie817f6bc052165a1aa038766d555bf414a12b626
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 b89cc55..84ca4fc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -206,6 +206,16 @@
     return accessFlags;
   }
 
+  public DexType getArgumentType(int argumentIndex) {
+    if (isStatic()) {
+      return getReference().getParameter(argumentIndex);
+    }
+    if (argumentIndex == 0) {
+      return getHolderType();
+    }
+    return getReference().getParameter(argumentIndex - 1);
+  }
+
   public CompilationState getCompilationState() {
     return compilationState;
   }
@@ -596,6 +606,10 @@
     return (accessFlags.isPrivate() || accessFlags.isConstructor()) && !accessFlags.isStatic();
   }
 
+  public boolean isInstance() {
+    return !isStatic();
+  }
+
   @Override
   public boolean isStatic() {
     checkIfObsolete();
@@ -1544,7 +1558,10 @@
       annotations = from.annotations();
       code = from.code;
       compilationState = CompilationState.NOT_PROCESSED;
-      optimizationInfo = from.optimizationInfo.mutableCopy();
+      optimizationInfo =
+          from.optimizationInfo.isDefaultMethodOptimizationInfo()
+              ? DefaultMethodOptimizationInfo.getInstance()
+              : from.optimizationInfo.mutableCopy();
       kotlinMemberInfo = from.kotlinMemberInfo;
       classFileVersion = from.classFileVersion;
       this.d8R8Synthesized = d8R8Synthesized;
@@ -1559,6 +1576,13 @@
       }
     }
 
+    public Builder fixupOptimizationInfo(Consumer<UpdatableMethodOptimizationInfo> consumer) {
+      if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+        consumer.accept(optimizationInfo.asUpdatableMethodOptimizationInfo());
+      }
+      return this;
+    }
+
     public void setAccessFlags(MethodAccessFlags accessFlags) {
       this.accessFlags = accessFlags.copy();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
index d92af89..32e01d8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.inlining;
 
 import com.android.tools.r8.ir.code.InvokeMethod;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 /** Constraint that is always satisfied. */
 public class AlwaysSimpleInliningConstraint extends SimpleInliningConstraint {
@@ -27,4 +28,9 @@
   public boolean isSatisfied(InvokeMethod invoke) {
     return false;
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
index a568f38..a4a6a73 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 /** Constraint that is satisfied if a specific argument is always false. */
 public class BooleanFalseSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
@@ -31,4 +32,9 @@
     assert argument.getType().isInt();
     return argument.isConstBoolean(false);
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
index 37b61d7..f69381f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 /** Constraint that is satisfied if a specific argument is always true. */
 public class BooleanTrueSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
@@ -31,4 +32,9 @@
     assert argument.getType().isInt();
     return argument.isConstBoolean(true);
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
index d3c0fca..9407b7b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.inlining;
 
 import com.android.tools.r8.ir.code.InvokeMethod;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 /** Constraint that is never satisfied. */
 public class NeverSimpleInliningConstraint extends SimpleInliningConstraint {
@@ -26,4 +27,9 @@
   public boolean isSatisfied(InvokeMethod invoke) {
     return false;
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
index 6ccf6ca..ea41ccb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 /** Constraint that is satisfied if a specific argument is always non-null. */
 public class NotNullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
@@ -31,4 +32,13 @@
     assert argument.getType().isReferenceType() : invoke;
     return argument.isNeverNull();
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    if (unboxedArgumentIndices.contains(getArgumentIndex())) {
+      // TODO(b/176067541): Could be refined to an argument-equals-int constraint.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
index 39cae4b..76d075d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 /** Constraint that is satisfied if a specific argument is always null. */
 public class NullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
@@ -31,4 +32,13 @@
     assert argument.getType().isReferenceType();
     return argument.getType().isDefinitelyNull();
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    if (unboxedArgumentIndices.contains(getArgumentIndex())) {
+      // TODO(b/176067541): Could be refined to an argument-equals-int constraint.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
index db78fd2..b411c8b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.function.Supplier;
 
 public abstract class SimpleInliningConstraint {
@@ -104,4 +105,7 @@
     assert other.isArgumentConstraint() || other.isConjunction();
     return new SimpleInliningConstraintDisjunction(ImmutableList.of(this, other));
   }
+
+  public abstract SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
new file mode 100644
index 0000000..1a7e997
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
@@ -0,0 +1,190 @@
+// Copyright (c) 2020, 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.inlining;
+
+import static com.android.tools.r8.ir.code.Opcodes.GOTO;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.THROW;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * Analysis that given a method computes a constraint which is satisfied by a concrete call site
+ * only if the method becomes simple after inlining into the concrete call site.
+ *
+ * <p>Examples of simple inlining constraints are:
+ *
+ * <ul>
+ *   <li>Always simple,
+ *   <li>Never simple,
+ *   <li>Simple if argument i is {true, false, null, not-null},
+ *   <li>Simple if argument i is true and argument j is false, or if argument i is false.
+ * </ul>
+ */
+public class SimpleInliningConstraintAnalysis {
+
+  private final SimpleInliningConstraintFactory factory;
+  private final ProgramMethod method;
+  private final InternalOptions options;
+  private final int simpleInliningConstraintThreshold;
+
+  private final Set<BasicBlock> seen = Sets.newIdentityHashSet();
+
+  public SimpleInliningConstraintAnalysis(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method) {
+    this.factory = appView.simpleInliningConstraintFactory();
+    this.method = method;
+    this.options = appView.options();
+    this.simpleInliningConstraintThreshold = appView.options().simpleInliningConstraintThreshold;
+  }
+
+  public SimpleInliningConstraint analyzeCode(IRCode code) {
+    if (method.getReference().getArity() == 0) {
+      // The method does not have any parameters, so there is no need to analyze the method.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    if (options.debug) {
+      // Inlining is not enabled in debug mode.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    // Run a bounded depth-first traversal to collect the path constraints that lead to early
+    // returns.
+    InstructionIterator instructionIterator =
+        code.entryBlock().iterator(code.getNumberOfArguments());
+    return analyzeInstructionsInBlock(code.entryBlock(), 0, instructionIterator);
+  }
+
+  private SimpleInliningConstraint analyzeInstructionsInBlock(BasicBlock block, int depth) {
+    return analyzeInstructionsInBlock(block, depth, block.iterator());
+  }
+
+  private SimpleInliningConstraint analyzeInstructionsInBlock(
+      BasicBlock block, int instructionDepth, InstructionIterator instructionIterator) {
+    // If we reach a block that has already been seen, give up.
+    if (!seen.add(block)) {
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    // Move the instruction iterator forward to the block's jump instruction, while incrementing the
+    // instruction depth of the depth-first traversal.
+    Instruction instruction = instructionIterator.next();
+    while (!instruction.isJumpInstruction()) {
+      assert !instruction.isArgument();
+      assert !instruction.isDebugInstruction();
+      if (!instruction.isAssume()) {
+        instructionDepth += 1;
+      }
+      instruction = instructionIterator.next();
+    }
+
+    // If we have exceeded the threshold, then all paths from this instruction will not lead to any
+    // early exits, so return 'never'.
+    if (instructionDepth > simpleInliningConstraintThreshold) {
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    // Analyze the jump instruction.
+    // TODO(b/132600418): Extend to switch and throw instructions.
+    switch (instruction.opcode()) {
+      case IF:
+        If ifInstruction = instruction.asIf();
+        if (ifInstruction.isZeroTest()) {
+          Value lhs = ifInstruction.lhs().getAliasedValue();
+          if (lhs.isArgument() && !lhs.isThis()) {
+            int argumentIndex = lhs.getDefinition().asArgument().getIndex();
+            DexType argumentType = method.getDefinition().getArgumentType(argumentIndex);
+            int currentDepth = instructionDepth;
+
+            // Compute the constraint for which paths through the true target are guaranteed to exit
+            // early.
+            SimpleInliningConstraint trueTargetConstraint =
+                computeConstraintFromIfZeroTest(
+                        argumentIndex, argumentType, ifInstruction.getType())
+                    // Only recurse into the true target if the constraint from the if-instruction
+                    // is not 'never'.
+                    .lazyMeet(
+                        () ->
+                            analyzeInstructionsInBlock(
+                                ifInstruction.getTrueTarget(), currentDepth));
+
+            // Compute the constraint for which paths through the false target are guaranteed to
+            // exit early.
+            SimpleInliningConstraint fallthroughTargetConstraint =
+                computeConstraintFromIfZeroTest(
+                        argumentIndex, argumentType, ifInstruction.getType().inverted())
+                    // Only recurse into the false target if the constraint from the if-instruction
+                    // is not 'never'.
+                    .lazyMeet(
+                        () ->
+                            analyzeInstructionsInBlock(
+                                ifInstruction.fallthroughBlock(), currentDepth));
+
+            // Paths going through this basic block are guaranteed to exit early if the true target
+            // is guaranteed to exit early or the false target is.
+            return trueTargetConstraint.join(fallthroughTargetConstraint);
+          }
+        }
+        break;
+
+      case GOTO:
+        return analyzeInstructionsInBlock(instruction.asGoto().getTarget(), instructionDepth);
+
+      case RETURN:
+        return AlwaysSimpleInliningConstraint.getInstance();
+
+      case THROW:
+        return block.hasCatchHandlers()
+            ? NeverSimpleInliningConstraint.getInstance()
+            : AlwaysSimpleInliningConstraint.getInstance();
+
+      default:
+        break;
+    }
+
+    // Give up.
+    return NeverSimpleInliningConstraint.getInstance();
+  }
+
+  private SimpleInliningConstraint computeConstraintFromIfZeroTest(
+      int argumentIndex, DexType argumentType, If.Type type) {
+    switch (type) {
+      case EQ:
+        if (argumentType.isReferenceType()) {
+          return factory.createNullConstraint(argumentIndex);
+        }
+        if (argumentType.isBooleanType()) {
+          return factory.createBooleanFalseConstraint(argumentIndex);
+        }
+        return NeverSimpleInliningConstraint.getInstance();
+
+      case NE:
+        if (argumentType.isReferenceType()) {
+          return factory.createNotNullConstraint(argumentIndex);
+        }
+        if (argumentType.isBooleanType()) {
+          return factory.createBooleanTrueConstraint(argumentIndex);
+        }
+        return NeverSimpleInliningConstraint.getInstance();
+
+      default:
+        return NeverSimpleInliningConstraint.getInstance();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
index ccc7400..02a8536 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.analysis.inlining;
 
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.List;
 
 public class SimpleInliningConstraintConjunction extends SimpleInliningConstraint {
@@ -62,4 +64,17 @@
     }
     return true;
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    List<SimpleInliningConstraint> rewrittenConstraints =
+        ListUtils.mapOrElse(
+            constraints,
+            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices),
+            null);
+    if (rewrittenConstraints != null) {
+      return new SimpleInliningConstraintConjunction(rewrittenConstraints);
+    }
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
index f84e5cd..c069f37 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.analysis.inlining;
 
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.List;
 
 public class SimpleInliningConstraintDisjunction extends SimpleInliningConstraint {
@@ -62,4 +64,17 @@
     }
     return false;
   }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    List<SimpleInliningConstraint> rewrittenConstraints =
+        ListUtils.mapOrElse(
+            constraints,
+            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices),
+            null);
+    if (rewrittenConstraints != null) {
+      return new SimpleInliningConstraintDisjunction(rewrittenConstraints);
+    }
+    return this;
+  }
 }
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 8e7236c..01cf2ba 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
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1064,6 +1065,11 @@
     return nextInstructionNumber;
   }
 
+  public int getNumberOfArguments() {
+    return context().getReference().getArity()
+        + BooleanUtils.intValue(!context().getDefinition().isStatic());
+  }
+
   public List<Value> collectArguments() {
     return collectArguments(false);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 1ce0df3..166022e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -62,6 +61,7 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -394,7 +394,7 @@
                 enumClassesToUnbox, enumsToUnboxWithPackageRequirement, appBuilder)
             .build();
     enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
-    NestedGraphLens enumUnboxingLens =
+    EnumUnboxingLens enumUnboxingLens =
         new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
             .fixupTypeReferences();
     enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
@@ -430,8 +430,9 @@
           public void fixup(DexEncodedMethod method) {
             MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
             if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
-              optimizationInfo
-                  .asUpdatableMethodOptimizationInfo()
+              UpdatableMethodOptimizationInfo updatableOptimizationInfo =
+                  optimizationInfo.asUpdatableMethodOptimizationInfo();
+              updatableOptimizationInfo
                   .fixupClassTypeReferences(appView.graphLens()::lookupType, appView)
                   .fixupAbstractReturnValue(appView, appView.graphLens())
                   .fixupInstanceInitializerInfo(appView, appView.graphLens());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index f230dda..c64e51e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -142,9 +142,7 @@
 
     public EnumUnboxingLens build(
         DexItemFactory dexItemFactory, GraphLens previousLens, Set<DexType> unboxedEnums) {
-      if (typeMap.isEmpty() && newFieldSignatures.isEmpty() && originalMethodSignatures.isEmpty()) {
-        return null;
-      }
+      assert !typeMap.isEmpty();
       return new EnumUnboxingLens(
           typeMap,
           originalMethodSignatures.getInverseOneToOneMap().getForwardMap(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 92ce5a1..f2f70ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -16,9 +16,11 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -47,7 +49,7 @@
     this.enumUnboxerRewriter = enumUnboxerRewriter;
   }
 
-  GraphLens.NestedGraphLens fixupTypeReferences() {
+  EnumUnboxingLens fixupTypeReferences() {
     assert enumUnboxerRewriter != null;
     // Fix all methods and fields using enums to unbox.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -118,11 +120,14 @@
   }
 
   private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
-    DexProto newProto = fixupProto(method.proto());
+    DexProto oldProto = method.proto();
+    DexProto newProto = fixupProto(oldProto);
     if (newProto == method.proto()) {
       return method;
     }
     assert !method.isClassInitializer();
+    assert !method.isLibraryMethodOverride().isTrue()
+        : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
     // We add the $enumunboxing$ suffix to make sure we do not create a library override.
     String newMethodName =
         method.getName().toString() + (method.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
@@ -131,17 +136,28 @@
     int numberOfExtraNullParameters = newMethod.getArity() - method.method.getArity();
     boolean isStatic = method.isStatic();
     lensBuilder.move(method.method, newMethod, isStatic, isStatic, numberOfExtraNullParameters);
-    DexEncodedMethod newEncodedMethod =
-        method.toTypeSubstitutedMethod(
-            newMethod,
-            builder ->
-                builder
-                    .setCompilationState(method.getCompilationState())
-                    .setIsLibraryMethodOverrideIf(
-                        method.isNonPrivateVirtualMethod(), OptionalBool.FALSE));
-    assert !method.isLibraryMethodOverride().isTrue()
-        : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
-    return newEncodedMethod;
+    return method.toTypeSubstitutedMethod(
+        newMethod,
+        builder ->
+            builder
+                .setCompilationState(method.getCompilationState())
+                .setIsLibraryMethodOverrideIf(
+                    method.isNonPrivateVirtualMethod(), OptionalBool.FALSE)
+                .fixupOptimizationInfo(
+                    optimizationInfo -> {
+                      IntList unboxedArgumentIndices = new IntArrayList();
+                      int offset = BooleanUtils.intValue(method.isInstance());
+                      for (int i = 0; i < method.getReference().getArity(); i++) {
+                        if (oldProto.getParameter(i).isReferenceType()
+                            && newProto.getParameter(i).isPrimitiveType()) {
+                          unboxedArgumentIndices.add(i + offset);
+                        }
+                      }
+                      optimizationInfo.setSimpleInliningConstraint(
+                          optimizationInfo
+                              .getSimpleInliningConstraint()
+                              .rewrittenWithUnboxedArguments(unboxedArgumentIndices));
+                    }));
   }
 
   private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 00993d6..9868a64 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -60,6 +60,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
 import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintAnalysis;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -148,6 +149,7 @@
     if (options.enableInlining) {
       identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
+    computeSimpleInliningConstraint(method, code, feedback, timing);
     computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
     computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
     computeInstanceInitializerInfo(
@@ -967,6 +969,21 @@
     return true;
   }
 
+  private void computeSimpleInliningConstraint(
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    if (appView.options().enableSimpleInliningConstraints) {
+      timing.begin("Compute simple inlining constraint");
+      computeSimpleInliningConstraint(method, code, feedback);
+      timing.end();
+    }
+  }
+
+  private void computeSimpleInliningConstraint(
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
+    feedback.setSimpleInliningConstraint(
+        method, new SimpleInliningConstraintAnalysis(appView, method).analyzeCode(code));
+  }
+
   private void computeDynamicReturnType(
       DynamicTypeOptimization dynamicTypeOptimization,
       OptimizationFeedback feedback,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index bb270b9..cc2a612 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -52,7 +52,10 @@
       return Reason.SIMPLE;
     }
     if (callSiteInformation.hasSingleCallSite(target)) {
-      return Reason.SINGLE_CALLER;
+      if (appView.options().testing.validInliningReasons == null
+          || appView.options().testing.validInliningReasons.contains(Reason.SINGLE_CALLER)) {
+        return Reason.SINGLE_CALLER;
+      }
     }
     if (isDoubleInliningTarget(target)) {
       return Reason.DUAL_CALLER;
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 7e88513..62196ab 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -256,6 +256,8 @@
   public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true;
   public boolean enableInliningOfInvokesWithNullableReceivers = true;
   public boolean disableInliningOfLibraryMethodOverrides = true;
+  public boolean enableSimpleInliningConstraints = true;
+  public int simpleInliningConstraintThreshold = 0;
   public boolean enableClassInlining = true;
   public boolean enableClassStaticizer = true;
   public boolean enableInitializedClassesAnalysis = true;
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index be11b36..fe6feab 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -56,6 +56,34 @@
     return result;
   }
 
+  /**
+   * Rewrites the input list based on the given function. Returns the mapped list if any elements
+   * were rewritten, otherwise returns the original list.
+   */
+  public static <T> List<T> mapOrElse(List<T> list, Function<T, T> fn, List<T> defaultValue) {
+    ArrayList<T> result = null;
+    for (int i = 0; i < list.size(); i++) {
+      T oldElement = list.get(i);
+      T newElement = fn.apply(oldElement);
+      if (newElement == oldElement) {
+        if (result != null) {
+          result.add(oldElement);
+        }
+      } else {
+        if (result == null) {
+          result = new ArrayList<>(list.size());
+          for (int j = 0; j < i; j++) {
+            result.add(list.get(j));
+          }
+        }
+        if (newElement != null) {
+          result.add(newElement);
+        }
+      }
+    }
+    return result != null ? result : defaultValue;
+  }
+
   public static <T> Optional<T> removeFirstMatch(List<T> list, Predicate<T> element) {
     int index = firstIndexMatching(list, element);
     if (index >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 89880cc..c67225c 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -18,6 +18,12 @@
     return result;
   }
 
+  public static <T> Set<T> newIdentityHashSet(T[] elements) {
+    Set<T> result = Sets.newIdentityHashSet();
+    Collections.addAll(result, elements);
+    return result;
+  }
+
   public static <T> Set<T> newIdentityHashSet(Iterable<T> c) {
     Set<T> result = Sets.newIdentityHashSet();
     c.forEach(result::add);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index 04b870f..9ba1f75 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -91,7 +91,7 @@
 
     MethodSubject selfCheck = mainSubject.uniqueMethodWithName("selfCheck");
     assertThat(selfCheck, isPresent());
-    assertEquals(1, countCallToParamNullCheck(selfCheck));
+    assertEquals(0, countCallToParamNullCheck(selfCheck));
     assertEquals(1, countPrintCall(selfCheck));
     assertEquals(0, countThrow(selfCheck));
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java
new file mode 100644
index 0000000..2aef42a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, 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.conditionalsimpleinlining;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public abstract class ConditionalSimpleInliningTestBase extends TestBase {
+
+  protected final boolean enableSimpleInliningConstraints;
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{1}, simple inlining constraints: {0}")
+  public static Iterable<?> data() {
+    return buildParameters(
+        BooleanUtils.values(), TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public ConditionalSimpleInliningTestBase(
+      boolean enableSimpleInliningConstraints, TestParameters parameters) {
+    this.enableSimpleInliningConstraints = enableSimpleInliningConstraints;
+    this.parameters = parameters;
+  }
+
+  public void configure(R8FullTestBuilder testBuilder) {
+    testBuilder
+        .addOptionsModification(this::enableSimpleInliningConstraints)
+        .addOptionsModification(this::disableSingleCallerInlining)
+        .setMinApi(parameters.getApiLevel());
+  }
+
+  private void enableSimpleInliningConstraints(InternalOptions options) {
+    options.enableSimpleInliningConstraints = enableSimpleInliningConstraints;
+  }
+
+  // TODO(b/176066007): Introduce a @NeverSingleCallerInline instead.
+  private void disableSingleCallerInlining(InternalOptions options) {
+    assert options.testing.validInliningReasons == null;
+    options.testing.validInliningReasons = SetUtils.newIdentityHashSet(Inliner.Reason.values());
+    options.testing.validInliningReasons.remove(Inliner.Reason.SINGLE_CALLER);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java
new file mode 100644
index 0000000..3cebb62
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2020, 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.conditionalsimpleinlining;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ConditionalSimpleInliningWithEnumUnboxingTest
+    extends ConditionalSimpleInliningTestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ConditionalSimpleInliningWithEnumUnboxingTest(TestParameters parameters) {
+    super(true, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .apply(this::configure)
+        .addEnumUnboxingInspector(
+            inspector ->
+                inspector.assertUnboxedIf(parameters.isDexRuntime(), EnumUnboxingCandidate.class))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      EnumUnboxingCandidate value =
+          System.currentTimeMillis() > 0 ? EnumUnboxingCandidate.A : EnumUnboxingCandidate.B;
+      simpleIfNullTest(value);
+    }
+
+    static void simpleIfNullTest(EnumUnboxingCandidate arg) {
+      if (arg == null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+  }
+
+  enum EnumUnboxingCandidate {
+    A,
+    B;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
new file mode 100644
index 0000000..2f605ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2020, 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.conditionalsimpleinlining;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+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 com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimpleIfNullOrNotNullInliningTest extends ConditionalSimpleInliningTestBase {
+
+  private final Class<?> mainClass;
+
+  @Parameters(name = "{2}, main: {1}, simple inlining constraints: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(
+            TestClassEligibleForSimpleInlining.class, TestClassIneligibleForSimpleInlining.class),
+        TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public SimpleIfNullOrNotNullInliningTest(
+      boolean enableSimpleInliningConstraints, Class<?> mainClass, TestParameters parameters) {
+    super(enableSimpleInliningConstraints, parameters);
+    this.mainClass = mainClass;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(mainClass, TestMethods.class)
+        .addKeepMainRule(mainClass)
+        .apply(this::configure)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), mainClass)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private String getExpectedOutput() {
+    if (mainClass == TestClassEligibleForSimpleInlining.class) {
+      return "";
+    }
+    return StringUtils.times(StringUtils.lines("Hello world!"), 8);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject mainMethodSubject = inspector.clazz(mainClass).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertThat(mainMethodSubject, not(invokesMethodWithName("print")));
+
+    ClassSubject classSubject = inspector.clazz(TestMethods.class);
+    assertThat(classSubject, notIf(isPresent(), shouldBeEligibleForSimpleInlining()));
+
+    if (shouldBeEligibleForSimpleInlining()) {
+      return;
+    }
+
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfNullTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothNullTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfNotNullTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothNotNullTest"), isPresent());
+  }
+
+  private boolean shouldBeEligibleForSimpleInlining() {
+    return mainClass == TestClassEligibleForSimpleInlining.class && enableSimpleInliningConstraints;
+  }
+
+  static class TestClassEligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      Object notNull = new Object();
+      TestMethods.simpleIfNullTest(null);
+      TestMethods.simpleIfBothNullTest(null, null);
+      TestMethods.simpleIfNotNullTest(notNull);
+      TestMethods.simpleIfBothNotNullTest(notNull, notNull);
+    }
+  }
+
+  static class TestClassIneligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      Object notNull = new Object();
+      TestMethods.simpleIfNullTest(notNull);
+      TestMethods.simpleIfBothNullTest(null, notNull);
+      TestMethods.simpleIfBothNullTest(notNull, null);
+      TestMethods.simpleIfBothNullTest(notNull, notNull);
+      TestMethods.simpleIfNotNullTest(null);
+      TestMethods.simpleIfBothNotNullTest(null, notNull);
+      TestMethods.simpleIfBothNotNullTest(notNull, null);
+      TestMethods.simpleIfBothNotNullTest(null, null);
+    }
+  }
+
+  static class TestMethods {
+
+    static void simpleIfNullTest(Object o) {
+      if (o == null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    static void simpleIfBothNullTest(Object o1, Object o2) {
+      if (o1 == null && o2 == null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    static void simpleIfNotNullTest(Object o) {
+      if (o != null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    static void simpleIfBothNotNullTest(Object o1, Object o2) {
+      if (o1 != null && o2 != null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
new file mode 100644
index 0000000..6c0debe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
@@ -0,0 +1,187 @@
+// Copyright (c) 2020, 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.conditionalsimpleinlining;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+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 com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimpleIfTrueOrFalseInliningTest extends ConditionalSimpleInliningTestBase {
+
+  private final Class<?> mainClass;
+
+  @Parameters(name = "{2}, main: {1}, simple inlining constraints: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(
+            TestClassEligibleForSimpleInlining.class, TestClassIneligibleForSimpleInlining.class),
+        TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public SimpleIfTrueOrFalseInliningTest(
+      boolean enableSimpleInliningConstraints, Class<?> mainClass, TestParameters parameters) {
+    super(enableSimpleInliningConstraints, parameters);
+    this.mainClass = mainClass;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(mainClass, TestMethods.class)
+        .addKeepMainRule(mainClass)
+        .apply(this::configure)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), mainClass)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private String getExpectedOutput() {
+    if (mainClass == TestClassEligibleForSimpleInlining.class) {
+      return "";
+    }
+    return StringUtils.times(StringUtils.lines("Hello world!"), 8);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject mainMethodSubject = inspector.clazz(mainClass).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertThat(mainMethodSubject, not(invokesMethodWithName("print")));
+
+    ClassSubject classSubject = inspector.clazz(TestMethods.class);
+    assertThat(classSubject, notIf(isPresent(), shouldBeEligibleForSimpleInlining()));
+
+    if (shouldBeEligibleForSimpleInlining()) {
+      return;
+    }
+
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfTrueTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothTrueTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfFalseTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothFalseTest"), isPresent());
+  }
+
+  private boolean shouldBeEligibleForSimpleInlining() {
+    return mainClass == TestClassEligibleForSimpleInlining.class && enableSimpleInliningConstraints;
+  }
+
+  static class TestClassEligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      TestMethods.simpleIfTrueTest(true);
+      TestMethods.simpleIfBothTrueTest(true, true);
+      TestMethods.simpleIfFalseTest(false);
+      TestMethods.simpleIfBothFalseTest(false, false);
+    }
+  }
+
+  static class TestClassIneligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      TestMethods.simpleIfTrueTest(false);
+      TestMethods.simpleIfBothTrueTest(true, false);
+      TestMethods.simpleIfBothTrueTest(false, true);
+      TestMethods.simpleIfBothTrueTest(false, false);
+      TestMethods.simpleIfFalseTest(true);
+      TestMethods.simpleIfBothFalseTest(true, false);
+      TestMethods.simpleIfBothFalseTest(false, true);
+      TestMethods.simpleIfBothFalseTest(true, true);
+    }
+  }
+
+  static class TestMethods {
+
+    static void simpleIfTrueTest(boolean b) {
+      if (b) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    static void simpleIfBothTrueTest(boolean b1, boolean b2) {
+      if (b1 && b2) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    static void simpleIfFalseTest(boolean b) {
+      if (!b) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    static void simpleIfBothFalseTest(boolean b1, boolean b2) {
+      if (!b1 && !b2) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 654f3a1..7059ae1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -153,6 +153,10 @@
     return invokesMethod(null, null, name, null);
   }
 
+  public static Predicate<InstructionSubject> isInvokeWithTarget(MethodSubject target) {
+    return isInvokeWithTarget(target.getMethod().getReference());
+  }
+
   public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
     return instruction -> instruction.isInvoke() && instruction.getMethod() == target;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
index 2d41c1f..38f7e92 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.graph.DexItemFactory;
@@ -25,6 +26,15 @@
     return this;
   }
 
+  public EnumUnboxingInspector assertUnboxedIf(boolean condition, Class<? extends Enum<?>> clazz) {
+    if (condition) {
+      assertUnboxed(clazz);
+    } else {
+      assertNotUnboxed(clazz);
+    }
+    return this;
+  }
+
   @SafeVarargs
   public final EnumUnboxingInspector assertUnboxed(Class<? extends Enum<?>>... classes) {
     for (Class<? extends Enum<?>> clazz : classes) {
@@ -32,4 +42,9 @@
     }
     return this;
   }
+
+  public EnumUnboxingInspector assertNotUnboxed(Class<? extends Enum<?>> clazz) {
+    assertFalse(unboxedEnums.isUnboxedEnum(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
 }