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;
+ }
}