Merge "Trivial class inliner"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index aa99407..2d52c64 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -155,6 +155,7 @@
// Disable global optimizations.
options.enableMinification = false;
options.enableInlining = false;
+ options.enableClassInlining = false;
options.outline.enabled = false;
DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index bb7c37f..4c65134 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -333,6 +333,8 @@
// Disable some of R8 optimizations.
assert internal.enableInlining;
internal.enableInlining = false;
+ assert internal.enableClassInlining;
+ internal.enableClassInlining = false;
assert internal.enableSwitchMapRemoval;
internal.enableSwitchMapRemoval = false;
assert internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9d6e40d..dafa35a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -701,6 +701,7 @@
if (internal.debug) {
// TODO(zerny): Should we support inlining in debug mode? b/62937285
internal.enableInlining = false;
+ internal.enableClassInlining = false;
// TODO(zerny): Should we support outlining in debug mode? b/62937285
internal.outline.enabled = false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55bc2db..7daef42 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,15 +3,21 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -475,6 +481,40 @@
return result;
}
+ public boolean canTriggerStaticInitializer(DexType clazz) {
+ Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
+
+ // Process superclass chain.
+ while (clazz != null && clazz != dexItemFactory.objectType) {
+ DexClass definition = definitionFor(clazz);
+ if (definition == null || definition.hasClassInitializer()) {
+ return true; // Assume it *may* trigger if we didn't find the definition.
+ }
+ knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
+ clazz = definition.superType;
+ }
+
+ // Process interfaces.
+ Queue<DexType> queue = new ArrayDeque<>(knownInterfaces);
+ while (!queue.isEmpty()) {
+ DexType iface = queue.remove();
+ DexClass definition = definitionFor(iface);
+ if (definition == null || definition.hasClassInitializer()) {
+ return true; // Assume it *may* trigger if we didn't find the definition.
+ }
+ if (!definition.accessFlags.isInterface()) {
+ throw new Unreachable(iface.toSourceString() + " is expected to be an interface");
+ }
+
+ for (DexType superIface : definition.interfaces.values) {
+ if (knownInterfaces.add(superIface)) {
+ queue.add(superIface);
+ }
+ }
+ }
+ return false;
+ }
+
public interface ResolutionResult {
DexEncodedMethod asResultOfResolve();
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 0cabaf1..eae59a0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,9 +44,12 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
@@ -564,6 +567,8 @@
private boolean useIdentifierNameString = false;
private boolean checksNullReceiverBeforeAnySideEffect = false;
private boolean triggersClassInitBeforeAnySideEffect = false;
+ private Set<DexField> receiverOnlyUsedForReadingFields = null;
+ private Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects = null;
private OptimizationInfo() {
// Intentionally left empty.
@@ -600,6 +605,15 @@
return returnsConstant;
}
+ public boolean isReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+ return receiverOnlyUsedForReadingFields != null &&
+ fields.containsAll(receiverOnlyUsedForReadingFields);
+ }
+
+ public Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects() {
+ return onlyInitializesFieldsWithNoOtherSideEffects;
+ }
+
public long getReturnedConstant() {
assert returnsConstant();
return returnedConstant;
@@ -641,6 +655,19 @@
returnedConstant = value;
}
+ private void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+ receiverOnlyUsedForReadingFields = fields;
+ }
+
+ private void markOnlyInitializesFieldsWithNoOtherSideEffects(Map<DexField, Integer> mapping) {
+ if (mapping == null) {
+ onlyInitializesFieldsWithNoOtherSideEffects = null;
+ } else {
+ onlyInitializesFieldsWithNoOtherSideEffects = mapping.isEmpty()
+ ? Collections.emptyMap() : ImmutableMap.copyOf(mapping);
+ }
+ }
+
private void markForceInline() {
forceInline = true;
}
@@ -698,6 +725,15 @@
ensureMutableOI().markReturnsConstant(value);
}
+ synchronized public void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+ ensureMutableOI().markReceiverOnlyUsedForReadingFields(fields);
+ }
+
+ synchronized public void markOnlyInitializesFieldsWithNoOtherSideEffects(
+ Map<DexField, Integer> mapping) {
+ ensureMutableOI().markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+ }
+
synchronized public void markForceInline() {
ensureMutableOI().markForceInline();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index b971ab4..8ea7b67 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -115,6 +115,7 @@
public final DexString valueOfMethodName = createString("valueOf");
public final DexString getClassMethodName = createString("getClass");
+ public final DexString finalizeMethodName = createString("finalize");
public final DexString ordinalMethodName = createString("ordinal");
public final DexString desiredAssertionStatusMethodName = createString("desiredAssertionStatus");
public final DexString forNameMethodName = createString("forName");
@@ -325,12 +326,15 @@
public final DexMethod getClass;
public final DexMethod constructor;
+ public final DexMethod finalize;
private ObjectMethods() {
getClass = createMethod(objectDescriptor, getClassMethodName, classDescriptor,
DexString.EMPTY_ARRAY);
constructor = createMethod(objectDescriptor,
constructorMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
+ finalize = createMethod(objectDescriptor,
+ finalizeMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
}
}
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 bb13495..1ec23d0 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
@@ -358,6 +358,15 @@
return true;
}
+ public boolean hasCatchHandlers() {
+ for (BasicBlock block : blocks) {
+ if (block.hasCatchHandlers()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean consistentDefUseChains() {
Set<Value> values = new HashSet<>();
@@ -578,16 +587,23 @@
}
public List<Value> collectArguments() {
+ return collectArguments(false);
+ }
+
+ public List<Value> collectArguments(boolean ignoreReceiver) {
final List<Value> arguments = new ArrayList<>();
Iterator<Instruction> iterator = blocks.get(0).iterator();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
if (instruction.isArgument()) {
- arguments.add(instruction.asArgument().outValue());
+ Value out = instruction.asArgument().outValue();
+ if (!ignoreReceiver || !out.isThis()) {
+ arguments.add(out);
+ }
}
}
assert arguments.size()
- == method.method.getArity() + (method.accessFlags.isStatic() ? 0 : 1);
+ == method.method.getArity() + ((method.accessFlags.isStatic() || ignoreReceiver) ? 0 : 1);
return arguments;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index d280dfe..81d4cdc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -131,6 +131,6 @@
@Override
public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
- return decider.computeForInvokePolymorpic(this, invocationContext);
+ return decider.computeForInvokePolymorphic(this, invocationContext);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 20cbd10..fe7cd10 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -87,6 +88,7 @@
private final LambdaRewriter lambdaRewriter;
private final InterfaceMethodRewriter interfaceMethodRewriter;
private final LambdaMerger lambdaMerger;
+ private final ClassInliner classInliner;
private final InternalOptions options;
private final CfgPrinter printer;
private final GraphLense graphLense;
@@ -160,6 +162,9 @@
this.identifierNameStringMarker = null;
this.devirtualizer = null;
}
+ this.classInliner =
+ (options.enableClassInlining && options.enableInlining && inliner != null)
+ ? new ClassInliner(appInfo.dexItemFactory) : null;
}
/**
@@ -686,6 +691,15 @@
assert code.isConsistentSSA();
}
+ if (classInliner != null) {
+ assert options.enableInlining && inliner != null;
+ classInliner.processMethodCode(
+ appInfo.withSubtyping(), method, code, isProcessedConcurrently,
+ methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline)
+ );
+ assert code.isConsistentSSA();
+ }
+
if (options.outline.enabled) {
outlineHandler.accept(code, method);
assert code.isConsistentSSA();
@@ -705,6 +719,12 @@
assert code.isConsistentSSA();
}
+ // Analysis must be done after method is rewritten by logArgumentTypes()
+ codeRewriter.identifyReceiverOnlyUsedForReadingFields(method, code, feedback);
+ if (method.isInstanceInitializer()) {
+ codeRewriter.identifyOnlyInitializesFieldsWithNoOtherSideEffects(method, code, feedback);
+ }
+
printMethod(code, "Optimized IR (SSA)");
finalizeIR(method, code, feedback);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index b2c5cfd..26ca6b2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -5,7 +5,10 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
public interface OptimizationFeedback {
void methodReturnsArgument(DexEncodedMethod method, int argument);
@@ -15,4 +18,7 @@
void markProcessed(DexEncodedMethod method, Constraint state);
void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
+ void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields);
+ void markOnlyInitializesFieldsWithNoOtherSideEffects(
+ DexEncodedMethod method, Map<DexField, Integer> mapping);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index ac1972b..645df64 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -5,7 +5,10 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
public class OptimizationFeedbackDirect implements OptimizationFeedback {
@@ -43,4 +46,15 @@
public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
method.markTriggerClassInitBeforeAnySideEffect(mark);
}
+
+ @Override
+ public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
+ method.markReceiverOnlyUsedForReadingFields(fields);
+ }
+
+ @Override
+ public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
+ Map<DexField, Integer> mapping) {
+ method.markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 80cb04f..97fe052 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -5,7 +5,10 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
public class OptimizationFeedbackIgnore implements OptimizationFeedback {
@@ -29,4 +32,13 @@
@Override
public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
+
+ @Override
+ public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
+ }
+
+ @Override
+ public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
+ Map<DexField, Integer> mapping) {
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 85a3ac6..e5c8962 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -47,10 +47,13 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -733,6 +736,122 @@
}
}
+ public void identifyReceiverOnlyUsedForReadingFields(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ if (!method.isNonAbstractVirtualMethod()) {
+ return;
+ }
+
+ feedback.markReceiverOnlyUsedForReadingFields(method, null);
+
+ Value instance = code.getThis();
+ if (instance.numberOfPhiUsers() > 0) {
+ return;
+ }
+
+ Set<DexField> fields = Sets.newIdentityHashSet();
+ for (Instruction insn : instance.uniqueUsers()) {
+ if (!insn.isInstanceGet()) {
+ return;
+ }
+ InstanceGet get = insn.asInstanceGet();
+ if (get.dest() == instance || get.object() != instance) {
+ return;
+ }
+ fields.add(get.getField());
+ }
+ feedback.markReceiverOnlyUsedForReadingFields(method, fields);
+ }
+
+ public void identifyOnlyInitializesFieldsWithNoOtherSideEffects(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ assert method.isInstanceInitializer();
+
+ feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, null);
+
+ if (code.hasCatchHandlers()) {
+ return;
+ }
+
+ List<Value> args = code.collectArguments(true /* not interested in receiver */);
+ Map<DexField, Integer> mapping = new IdentityHashMap<>();
+ Value receiver = code.getThis();
+
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+
+ // Mark an argument.
+ if (instruction.isArgument()) {
+ continue;
+ }
+
+ // Allow super call to java.lang.Object.<init>() on 'this'.
+ if (instruction.isInvokeDirect()) {
+ InvokeDirect invokedDirect = instruction.asInvokeDirect();
+ if (invokedDirect.getInvokedMethod() != dexItemFactory.objectMethods.constructor ||
+ invokedDirect.getReceiver() != receiver) {
+ return;
+ }
+ continue;
+ }
+
+ // Allow final return.
+ if (instruction.isReturn()) {
+ continue;
+ }
+
+ // Allow assignment to this class' fields. If the assigned value is an argument
+ // reference update the mep. Otherwise just allow assigning any value, since all
+ // invalid values should be filtered out at the definitions.
+ if (instruction.isInstancePut()) {
+ InstancePut instancePut = instruction.asInstancePut();
+ DexField field = instancePut.getField();
+ if (instancePut.object() != receiver) {
+ return;
+ }
+
+ Value value = instancePut.value();
+ if (value.isArgument() && !value.isThis()) {
+ assert (args.contains(value));
+ mapping.put(field, args.indexOf(value));
+ } else {
+ mapping.remove(field);
+ }
+ continue;
+ }
+
+ // Allow non-throwing constants.
+ if (instruction.isConstInstruction()) {
+ if (instruction.instructionTypeCanThrow()) {
+ return;
+ }
+ continue;
+ }
+
+ // Allow goto instructions jumping to the next block.
+ if (instruction.isGoto()) {
+ if (instruction.getBlock().getNumber() + 1 !=
+ instruction.asGoto().getTarget().getNumber()) {
+ return;
+ }
+ continue;
+ }
+
+ // Allow binary and unary instructions if they don't throw.
+ if (instruction.isBinop() || instruction.isUnop()) {
+ if (instruction.instructionTypeCanThrow()) {
+ return;
+ }
+ continue;
+ }
+
+ return;
+ }
+
+ feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, mapping);
+ }
+
/**
* An enum used to classify instructions according to a particular effect that they produce.
*
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
new file mode 100644
index 0000000..90081d1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -0,0 +1,351 @@
+// Copyright (c) 2018, 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;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.logging.Log;
+import java.util.ListIterator;
+import java.util.function.Predicate;
+
+final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
+
+ private final Inliner inliner;
+ private final DexEncodedMethod method;
+ private final IRCode code;
+ private final TypeEnvironment typeEnvironment;
+ private final CallSiteInformation callSiteInformation;
+ private final Predicate<DexEncodedMethod> isProcessedConcurrently;
+ private final InliningInfo info;
+ private final int inliningInstructionLimit;
+ private int instructionAllowance;
+
+ DefaultInliningOracle(
+ Inliner inliner,
+ DexEncodedMethod method,
+ IRCode code,
+ TypeEnvironment typeEnvironment,
+ CallSiteInformation callSiteInformation,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ int inliningInstructionLimit,
+ int inliningInstructionAllowance) {
+ this.inliner = inliner;
+ this.method = method;
+ this.code = code;
+ this.typeEnvironment = typeEnvironment;
+ this.callSiteInformation = callSiteInformation;
+ this.isProcessedConcurrently = isProcessedConcurrently;
+ info = Log.ENABLED ? new InliningInfo(method) : null;
+ this.inliningInstructionLimit = inliningInstructionLimit;
+ this.instructionAllowance = inliningInstructionAllowance;
+ }
+
+ @Override
+ public void finish() {
+ if (Log.ENABLED) {
+ Log.debug(getClass(), info.toString());
+ }
+ }
+
+ private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
+ DexEncodedMethod candidate =
+ invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
+ if ((candidate == null)
+ || (candidate.getCode() == null)
+ || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
+ if (info != null) {
+ info.exclude(invoke, "No inlinee");
+ }
+ return null;
+ }
+ // Ignore the implicit receiver argument.
+ int numberOfArguments =
+ invoke.arguments().size() - (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
+ if (numberOfArguments != candidate.method.getArity()) {
+ if (info != null) {
+ info.exclude(invoke, "Argument number mismatch");
+ }
+ return null;
+ }
+ return candidate;
+ }
+
+ private Reason computeInliningReason(DexEncodedMethod target) {
+ if (target.getOptimizationInfo().forceInline()) {
+ return Reason.FORCE;
+ }
+ if (inliner.appInfo.hasLiveness()
+ && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
+ return Reason.ALWAYS;
+ }
+ if (callSiteInformation.hasSingleCallSite(target)) {
+ return Reason.SINGLE_CALLER;
+ }
+ if (isDoubleInliningTarget(target)) {
+ return Reason.DUAL_CALLER;
+ }
+ return Reason.SIMPLE;
+ }
+
+ private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
+ // Only proceed with inlining a static invoke if:
+ // - the holder for the target equals the holder for the method, or
+ // - the target method always triggers class initialization of its holder before any other side
+ // effect (hence preserving class initialization semantics).
+ // - there is no non-trivial class initializer.
+ DexType targetHolder = target.method.getHolder();
+ if (method.method.getHolder() == targetHolder) {
+ return true;
+ }
+ DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
+ assert clazz != null;
+ if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
+ return true;
+ }
+ return classInitializationHasNoSideffects(targetHolder);
+ }
+
+ /**
+ * Check for class initializer side effects when loading this class, as inlining might remove the
+ * load operation.
+ * <p>
+ * See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
+ * <p>
+ * For simplicity, we are conservative and consider all interfaces, not only the ones with default
+ * methods.
+ */
+ private boolean classInitializationHasNoSideffects(DexType classToCheck) {
+ DexClass clazz = inliner.appInfo.definitionFor(classToCheck);
+ if ((clazz == null)
+ || clazz.hasNonTrivialClassInitializer()
+ || clazz.defaultValuesForStaticFieldsMayTriggerAllocation()) {
+ return false;
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ if (!classInitializationHasNoSideffects(iface)) {
+ return false;
+ }
+ }
+ return clazz.superType == null || classInitializationHasNoSideffects(clazz.superType);
+ }
+
+ private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
+ // 10 is found from measuring.
+ return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
+ && candidate.getCode().estimatedSizeForInliningAtMost(10);
+ }
+
+ private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
+ Reason reason) {
+ if (method == candidate) {
+ // Cannot handle recursive inlining at this point.
+ // Force inlined method should never be recursive.
+ assert !candidate.getOptimizationInfo().forceInline();
+ if (info != null) {
+ info.exclude(invoke, "direct recursion");
+ }
+ return false;
+ }
+
+ if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
+ if (info != null) {
+ info.exclude(invoke, "is processed in parallel");
+ }
+ return false;
+ }
+
+ // Abort inlining attempt if method -> target access is not right.
+ if (!inliner.hasInliningAccess(method, candidate)) {
+ if (info != null) {
+ info.exclude(invoke, "target does not have right access");
+ }
+ return false;
+ }
+
+ DexClass holder = inliner.appInfo.definitionFor(candidate.method.getHolder());
+ if (holder.isInterface()) {
+ // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
+ // runtime.
+ if (info != null) {
+ info.exclude(invoke, "Do not inline target if method holder is an interface class");
+ }
+ return false;
+ }
+
+ if (holder.isLibraryClass()) {
+ // Library functions should not be inlined.
+ return false;
+ }
+
+ // Don't inline if target is synchronized.
+ if (candidate.accessFlags.isSynchronized()) {
+ if (info != null) {
+ info.exclude(invoke, "target is synchronized");
+ }
+ return false;
+ }
+
+ // Attempt to inline a candidate that is only called twice.
+ if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, candidate) == null)) {
+ if (info != null) {
+ info.exclude(invoke, "target is not ready for double inlining");
+ }
+ return false;
+ }
+
+ if (reason == Reason.SIMPLE) {
+ // If we are looking for a simple method, only inline if actually simple.
+ Code code = candidate.getCode();
+ if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public InlineAction computeForInvokeWithReceiver(
+ InvokeMethodWithReceiver invoke, DexType invocationContext) {
+ DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+ if (candidate == null || inliner.isBlackListed(candidate.method)) {
+ return null;
+ }
+
+ // We can only inline an instance method call if we preserve the null check semantic (which
+ // would throw NullPointerException if the receiver is null). Therefore we can inline only if
+ // one of the following conditions is true:
+ // * the candidate inlinee checks null receiver before any side effect
+ // * the receiver is known to be non-null
+ boolean receiverIsNeverNull =
+ !typeEnvironment.getLatticeElement(invoke.getReceiver()).isNullable();
+ if (!receiverIsNeverNull
+ && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
+ if (info != null) {
+ info.exclude(invoke, "receiver for candidate can be null");
+ }
+ return null;
+ }
+
+ Reason reason = computeInliningReason(candidate);
+ if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+ // Abort inlining attempt if the single target is not an inlining candidate.
+ if (info != null) {
+ info.exclude(invoke, "target is not identified for inlining");
+ }
+ return null;
+ }
+
+ if (!passesInliningConstraints(invoke, candidate, reason)) {
+ return null;
+ }
+
+ if (info != null) {
+ info.include(invoke.getType(), candidate);
+ }
+ return new InlineAction(candidate, invoke, reason);
+ }
+
+ @Override
+ public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+ DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+ if (candidate == null || inliner.isBlackListed(candidate.method)) {
+ return null;
+ }
+
+ Reason reason = computeInliningReason(candidate);
+ // Determine if this should be inlined no matter how big it is.
+ if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+ // Abort inlining attempt if the single target is not an inlining candidate.
+ if (info != null) {
+ info.exclude(invoke, "target is not identified for inlining");
+ }
+ return null;
+ }
+
+ // Abort inlining attempt if we can not guarantee class for static target has been initialized.
+ if (!canInlineStaticInvoke(method, candidate)) {
+ if (info != null) {
+ info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
+ }
+ return null;
+ }
+
+ if (!passesInliningConstraints(invoke, candidate, reason)) {
+ return null;
+ }
+
+ if (info != null) {
+ info.include(invoke.getType(), candidate);
+ }
+ return new InlineAction(candidate, invoke, reason);
+ }
+
+ @Override
+ public InlineAction computeForInvokePolymorphic(
+ InvokePolymorphic invoke, DexType invocationContext) {
+ // TODO: No inlining of invoke polymorphic for now.
+ if (info != null) {
+ info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
+ }
+ return null;
+ }
+
+ @Override
+ public void ensureMethodProcessed(
+ DexEncodedMethod target, IRCode inlinee) throws ApiLevelException {
+ if (!target.isProcessed()) {
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
+ }
+ inliner.performInlining(
+ target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
+ }
+ }
+
+ @Override
+ public boolean exceededAllowance() {
+ return instructionAllowance < 0;
+ }
+
+ @Override
+ public void markInlined(IRCode inlinee) {
+ instructionAllowance -= inliner.numberOfInstructions(inlinee);
+ }
+
+ @Override
+ public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+ ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
+ if (inliner.options.enableNonNullTracking) {
+ // Move the cursor back to where the inlinee blocks are added.
+ blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
+ // Kick off the tracker to add non-null IRs only to the inlinee blocks.
+ new NonNullTracker()
+ .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
+ // Move the cursor forward to where the inlinee blocks end.
+ blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
+ }
+ // Update type env for inlined blocks.
+ typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
+ // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
+ return blockIterator;
+ }
+
+ @Override
+ public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
+ return null; // Maybe improve later.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
new file mode 100644
index 0000000..86d40d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2018, 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;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import java.util.ListIterator;
+import java.util.Map;
+
+final class ForcedInliningOracle implements InliningOracle, InliningStrategy {
+ private final DexEncodedMethod method;
+ private final Map<InvokeMethodWithReceiver, Inliner.InliningInfo> invokesToInline;
+
+ ForcedInliningOracle(DexEncodedMethod method,
+ Map<InvokeMethodWithReceiver, Inliner.InliningInfo> invokesToInline) {
+ this.method = method;
+ this.invokesToInline = invokesToInline;
+ }
+
+ @Override
+ public void finish() {
+ }
+
+ @Override
+ public InlineAction computeForInvokeWithReceiver(
+ InvokeMethodWithReceiver invoke, DexType invocationContext) {
+ Inliner.InliningInfo info = invokesToInline.get(invoke);
+ if (info == null) {
+ return null;
+ }
+
+ assert method != info.target;
+ return new InlineAction(info.target, invoke, Reason.FORCE);
+ }
+
+ @Override
+ public InlineAction computeForInvokeStatic(
+ InvokeStatic invoke, DexType invocationContext) {
+ return null; // Not yet supported.
+ }
+
+ @Override
+ public InlineAction computeForInvokePolymorphic(
+ InvokePolymorphic invoke, DexType invocationContext) {
+ return null; // Not yet supported.
+ }
+
+ @Override
+ public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
+ assert target.isProcessed();
+ }
+
+ @Override
+ public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+ ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
+ return blockIterator;
+ }
+
+ @Override
+ public boolean exceededAllowance() {
+ return false; // Never exceeds allowance.
+ }
+
+ @Override
+ public void markInlined(IRCode inlinee) {
+ }
+
+ @Override
+ public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
+ assert invoke.isInvokeMethodWithReceiver();
+ Inliner.InliningInfo info = invokesToInline.get(invoke.asInvokeMethodWithReceiver());
+ assert info != null;
+ return info.receiverType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index d6efdbb..a86130f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -28,7 +29,6 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
-import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -43,10 +43,11 @@
import java.util.stream.Collectors;
public class Inliner {
+ private static final int INITIAL_INLINING_INSTRUCTION_ALLOWANCE = 1500;
protected final AppInfoWithLiveness appInfo;
private final GraphLense graphLense;
- private final InternalOptions options;
+ final InternalOptions options;
// State for inlining methods which are known to be called twice.
private boolean applyDoubleInlining = false;
@@ -278,7 +279,7 @@
}
}
- private int numberOfInstructions(IRCode code) {
+ final int numberOfInstructions(IRCode code) {
int numOfInstructions = 0;
for (BasicBlock block : code.blocks) {
numOfInstructions += block.getInstructions().size();
@@ -364,6 +365,23 @@
return true;
}
+ public static class InliningInfo {
+ public final DexEncodedMethod target;
+ public final DexType receiverType; // null, if unknown
+
+ public InliningInfo(DexEncodedMethod target, DexType receiverType) {
+ this.target = target;
+ this.receiverType = receiverType;
+ }
+ }
+
+ public void performForcedInlining(DexEncodedMethod method, IRCode code,
+ Map<InvokeMethodWithReceiver, InliningInfo> invokesToInline) throws ApiLevelException {
+
+ ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline);
+ performInliningImpl(oracle, oracle, method, code);
+ }
+
public void performInlining(
DexEncodedMethod method,
IRCode code,
@@ -371,29 +389,40 @@
Predicate<DexEncodedMethod> isProcessedConcurrently,
CallSiteInformation callSiteInformation)
throws ApiLevelException {
- int instruction_allowance = 1500;
- instruction_allowance -= numberOfInstructions(code);
- if (instruction_allowance < 0) {
- return;
- }
- InliningOracle oracle =
- new InliningOracle(
+
+ DefaultInliningOracle oracle =
+ new DefaultInliningOracle(
this,
method,
+ code,
typeEnvironment,
callSiteInformation,
isProcessedConcurrently,
- options.inliningInstructionLimit);
+ options.inliningInstructionLimit,
+ INITIAL_INLINING_INSTRUCTION_ALLOWANCE - numberOfInstructions(code));
+
+ performInliningImpl(oracle, oracle, method, code);
+ }
+
+ private void performInliningImpl(
+ InliningStrategy strategy,
+ InliningOracle oracle,
+ DexEncodedMethod method,
+ IRCode code)
+ throws ApiLevelException {
+ if (strategy.exceededAllowance()) {
+ return;
+ }
List<BasicBlock> blocksToRemove = new ArrayList<>();
ListIterator<BasicBlock> blockIterator = code.listIterator();
- while (blockIterator.hasNext() && (instruction_allowance >= 0)) {
+ while (blockIterator.hasNext() && !strategy.exceededAllowance()) {
BasicBlock block = blockIterator.next();
if (blocksToRemove.contains(block)) {
continue;
}
InstructionListIterator iterator = block.listIterator();
- while (iterator.hasNext() && (instruction_allowance >= 0)) {
+ while (iterator.hasNext() && !strategy.exceededAllowance()) {
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
@@ -416,49 +445,27 @@
if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
continue;
}
+
// If this code did not go through the full pipeline, apply inlining to make sure
// that force inline targets get processed.
- if (!target.isProcessed()) {
- assert result.reason == Reason.FORCE;
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
- }
- performInlining(
- target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
- }
+ strategy.ensureMethodProcessed(target, inlinee);
+
// Make sure constructor inlining is legal.
assert !target.isClassInitializer();
if (target.isInstanceInitializer()
&& !legalConstructorInline(method, invoke, inlinee)) {
continue;
}
- DexType downcast = null;
- if (invoke.isInvokeMethodWithReceiver()) {
- // If the invoke has a receiver but the declared method holder is different
- // from the computed target holder, inlining requires a downcast of the receiver.
- if (target.method.getHolder() != invoke.getInvokedMethod().getHolder()) {
- downcast = result.target.method.getHolder();
- }
- }
+ DexType downcast = createDowncastIfNeeded(strategy, invoke, target);
// Inline the inlinee code in place of the invoke instruction
// Back up before the invoke instruction.
iterator.previous();
- instruction_allowance -= numberOfInstructions(inlinee);
- if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
+ strategy.markInlined(inlinee);
+ if (!strategy.exceededAllowance() || result.ignoreInstructionBudget()) {
BasicBlock invokeSuccessor =
iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
- if (options.enableNonNullTracking) {
- // Move the cursor back to where the inlinee blocks are added.
- blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
- // Kick off the tracker to add non-null IRs only to the inlinee blocks.
- new NonNullTracker()
- .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
- // Move the cursor forward to where the inlinee blocks end.
- blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
- }
- // Update type env for inlined blocks.
- typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
- // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
+ blockIterator = strategy.
+ updateTypeInformationIfNeeded(inlinee, blockIterator, block, invokeSuccessor);
// If we inlined the invoke from a bridge method, it is no longer a bridge method.
if (method.accessFlags.isBridge()) {
@@ -481,4 +488,22 @@
code.removeAllTrivialPhis();
assert code.isConsistentSSA();
}
+
+ private DexType createDowncastIfNeeded(
+ InliningStrategy strategy, InvokeMethod invoke, DexEncodedMethod target) {
+ if (invoke.isInvokeMethodWithReceiver()) {
+ // If the invoke has a receiver but the actual type of the receiver is different
+ // from the computed target holder, inlining requires a downcast of the receiver.
+ DexType assumedReceiverType = strategy.getReceiverTypeIfKnown(invoke);
+ if (assumedReceiverType == null) {
+ // In case we don't know exact type of the receiver we use declared
+ // method holder as a fallback.
+ assumedReceiverType = invoke.getInvokedMethod().getHolder();
+ }
+ if (assumedReceiverType != target.method.getHolder()) {
+ return target.method.getHolder();
+ }
+ }
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index beb4caa..08f8045 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -1,296 +1,28 @@
// Copyright (c) 2017, 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;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
-import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokePolymorphic;
import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.logging.Log;
-import java.util.function.Predicate;
/**
- * The InliningOracle contains information needed for when inlining
- * other methods into @method.
+ * The InliningOracle contains information needed for when inlining other methods into @method.
*/
-public class InliningOracle {
+public interface InliningOracle {
- private final Inliner inliner;
- private final DexEncodedMethod method;
- private final TypeEnvironment typeEnvironment;
- private final CallSiteInformation callSiteInformation;
- private final Predicate<DexEncodedMethod> isProcessedConcurrently;
- private final InliningInfo info;
- private final int inliningInstructionLimit;
+ void finish();
- InliningOracle(
- Inliner inliner,
- DexEncodedMethod method,
- TypeEnvironment typeEnvironment,
- CallSiteInformation callSiteInformation,
- Predicate<DexEncodedMethod> isProcessedConcurrently,
- int inliningInstructionLimit) {
- this.inliner = inliner;
- this.method = method;
- this.typeEnvironment = typeEnvironment;
- this.callSiteInformation = callSiteInformation;
- this.isProcessedConcurrently = isProcessedConcurrently;
- info = Log.ENABLED ? new InliningInfo(method) : null;
- this.inliningInstructionLimit = inliningInstructionLimit;
- }
+ InlineAction computeForInvokeWithReceiver(
+ InvokeMethodWithReceiver invoke, DexType invocationContext);
- void finish() {
- if (Log.ENABLED) {
- Log.debug(getClass(), info.toString());
- }
- }
+ InlineAction computeForInvokeStatic(
+ InvokeStatic invoke, DexType invocationContext);
- private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
- DexEncodedMethod candidate =
- invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
- if ((candidate == null)
- || (candidate.getCode() == null)
- || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
- if (info != null) {
- info.exclude(invoke, "No inlinee");
- }
- return null;
- }
- // Ignore the implicit receiver argument.
- int numberOfArguments =
- invoke.arguments().size() - (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
- if (numberOfArguments != candidate.method.getArity()) {
- if (info != null) {
- info.exclude(invoke, "Argument number mismatch");
- }
- return null;
- }
- return candidate;
- }
-
- private Reason computeInliningReason(DexEncodedMethod target) {
- if (target.getOptimizationInfo().forceInline()) {
- return Reason.FORCE;
- }
- if (inliner.appInfo.hasLiveness()
- && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
- return Reason.ALWAYS;
- }
- if (callSiteInformation.hasSingleCallSite(target)) {
- return Reason.SINGLE_CALLER;
- }
- if (isDoubleInliningTarget(target)) {
- return Reason.DUAL_CALLER;
- }
- return Reason.SIMPLE;
- }
-
- private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
- // Only proceed with inlining a static invoke if:
- // - the holder for the target equals the holder for the method, or
- // - the target method always triggers class initialization of its holder before any other side
- // effect (hence preserving class initialization semantics).
- // - there is no non-trivial class initializer.
- DexType targetHolder = target.method.getHolder();
- if (method.method.getHolder() == targetHolder) {
- return true;
- }
- DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
- assert clazz != null;
- if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
- return true;
- }
- return classInitializationHasNoSideffects(targetHolder);
- }
-
- /**
- * Check for class initializer side effects when loading this class, as inlining might remove the
- * load operation.
- * <p>
- * See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
- * <p>
- * For simplicity, we are conservative and consider all interfaces, not only the ones with default
- * methods.
- */
- private boolean classInitializationHasNoSideffects(DexType classToCheck) {
- DexClass clazz = inliner.appInfo.definitionFor(classToCheck);
- if ((clazz == null)
- || clazz.hasNonTrivialClassInitializer()
- || clazz.defaultValuesForStaticFieldsMayTriggerAllocation()) {
- return false;
- }
- for (DexType iface : clazz.interfaces.values) {
- if (!classInitializationHasNoSideffects(iface)) {
- return false;
- }
- }
- return clazz.superType == null || classInitializationHasNoSideffects(clazz.superType);
- }
-
- private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
- // 10 is found from measuring.
- return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
- && candidate.getCode().estimatedSizeForInliningAtMost(10);
- }
-
- private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
- Reason reason) {
- if (method == candidate) {
- // Cannot handle recursive inlining at this point.
- // Force inlined method should never be recursive.
- assert !candidate.getOptimizationInfo().forceInline();
- if (info != null) {
- info.exclude(invoke, "direct recursion");
- }
- return false;
- }
-
- if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
- if (info != null) {
- info.exclude(invoke, "is processed in parallel");
- }
- return false;
- }
-
- // Abort inlining attempt if method -> target access is not right.
- if (!inliner.hasInliningAccess(method, candidate)) {
- if (info != null) {
- info.exclude(invoke, "target does not have right access");
- }
- return false;
- }
-
- DexClass holder = inliner.appInfo.definitionFor(candidate.method.getHolder());
- if (holder.isInterface()) {
- // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
- // runtime.
- if (info != null) {
- info.exclude(invoke, "Do not inline target if method holder is an interface class");
- }
- return false;
- }
-
- if (holder.isLibraryClass()) {
- // Library functions should not be inlined.
- return false;
- }
-
- // Don't inline if target is synchronized.
- if (candidate.accessFlags.isSynchronized()) {
- if (info != null) {
- info.exclude(invoke, "target is synchronized");
- }
- return false;
- }
-
- // Attempt to inline a candidate that is only called twice.
- if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, candidate) == null)) {
- if (info != null) {
- info.exclude(invoke, "target is not ready for double inlining");
- }
- return false;
- }
-
- if (reason == Reason.SIMPLE) {
- // If we are looking for a simple method, only inline if actually simple.
- Code code = candidate.getCode();
- if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
- return false;
- }
- }
- return true;
- }
-
- public InlineAction computeForInvokeWithReceiver(
- InvokeMethodWithReceiver invoke, DexType invocationContext) {
- DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
- if (candidate == null || inliner.isBlackListed(candidate.method)) {
- return null;
- }
-
- // We can only inline an instance method call if we preserve the null check semantic (which
- // would throw NullPointerException if the receiver is null). Therefore we can inline only if
- // one of the following conditions is true:
- // * the candidate inlinee checks null receiver before any side effect
- // * the receiver is known to be non-null
- boolean receiverIsNeverNull =
- !typeEnvironment.getLatticeElement(invoke.getReceiver()).isNullable();
- if (!receiverIsNeverNull
- && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
- if (info != null) {
- info.exclude(invoke, "receiver for candidate can be null");
- }
- return null;
- }
-
- Reason reason = computeInliningReason(candidate);
- if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
- // Abort inlining attempt if the single target is not an inlining candidate.
- if (info != null) {
- info.exclude(invoke, "target is not identified for inlining");
- }
- return null;
- }
-
- if (!passesInliningConstraints(invoke, candidate, reason)) {
- return null;
- }
-
- if (info != null) {
- info.include(invoke.getType(), candidate);
- }
- return new InlineAction(candidate, invoke, reason);
- }
-
- public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
- DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
- if (candidate == null || inliner.isBlackListed(candidate.method)) {
- return null;
- }
-
- Reason reason = computeInliningReason(candidate);
- // Determine if this should be inlined no matter how big it is.
- if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
- // Abort inlining attempt if the single target is not an inlining candidate.
- if (info != null) {
- info.exclude(invoke, "target is not identified for inlining");
- }
- return null;
- }
-
- // Abort inlining attempt if we can not guarantee class for static target has been initialized.
- if (!canInlineStaticInvoke(method, candidate)) {
- if (info != null) {
- info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
- }
- return null;
- }
-
- if (!passesInliningConstraints(invoke, candidate, reason)) {
- return null;
- }
-
- if (info != null) {
- info.include(invoke.getType(), candidate);
- }
- return new InlineAction(candidate, invoke, reason);
- }
-
- public InlineAction computeForInvokePolymorpic(
- InvokePolymorphic invoke, DexType invocationContext) {
- // TODO: No inlining of invoke polymorphic for now.
- if (info != null) {
- info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
- }
- return null;
- }
+ InlineAction computeForInvokePolymorphic(
+ InvokePolymorphic invoke, DexType invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
new file mode 100644
index 0000000..2b0c7e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2018, 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;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import java.util.ListIterator;
+
+interface InliningStrategy {
+ boolean exceededAllowance();
+
+ void markInlined(IRCode inlinee);
+
+ void ensureMethodProcessed(
+ DexEncodedMethod target, IRCode inlinee) throws ApiLevelException;
+
+ ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+ ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor);
+
+ DexType getReceiverTypeIfKnown(InvokeMethod invoke);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
new file mode 100644
index 0000000..7d13b1a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -0,0 +1,385 @@
+// Copyright (c) 2018, 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.classinliner;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.Streams;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public final class ClassInliner {
+ private final DexItemFactory factory;
+ private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+
+ private static final Map<DexField, Integer> NO_MAPPING = new IdentityHashMap<>();
+
+ public interface InlinerAction {
+ void inline(Map<InvokeMethodWithReceiver, InliningInfo> methods) throws ApiLevelException;
+ }
+
+ public ClassInliner(DexItemFactory factory) {
+ this.factory = factory;
+ }
+
+ // Process method code and inline eligible class instantiations, in short:
+ //
+ // - collect all 'new-instance' instructions in the original code. Note that class
+ // inlining, if happens, mutates code and can add 'new-instance' instructions.
+ // Processing them as well is possible, but does not seem to have much value.
+ //
+ // - for each 'new-instance' we check if it is eligible for inlining, i.e:
+ // -> the class of the new instance is 'eligible' (see computeClassEligible(...))
+ // -> the instance is initialized with 'eligible' constructor (see
+ // onlyInitializesFieldsWithNoOtherSideEffects flag in method's optimization
+ // info); eligible constructor also defines a set of instance fields directly
+ // initialized with parameter values, called field initialization mapping below
+ // -> has only 'eligible' uses, i.e:
+ // * it is a receiver of a field read if the field is present in the
+ // field initialization mapping
+ // * it is a receiver of virtual or interface call with single target being
+ // a method only reading fields in the current field initialization mapping
+ //
+ // - inline eligible 'new-instance' instructions, i.e:
+ // -> force inline methods called on the instance (which may introduce additional
+ // instance field reads, but only for fields present in the current field
+ // initialization mapping)
+ // -> replace instance field reads with appropriate values passed to the constructor
+ // according to field initialization mapping
+ // -> remove constructor call
+ // -> remove 'new-instance' instructions
+ //
+ // For example:
+ //
+ // Original code:
+ // class C {
+ // static class L {
+ // final int x;
+ // L(int x) {
+ // this.x = x;
+ // }
+ // int getX() {
+ // return x;
+ // }
+ // }
+ // static int method1() {
+ // return new L(1).x;
+ // }
+ // static int method2() {
+ // return new L(1).getX();
+ // }
+ // }
+ //
+ // Code after class C is 'inlined':
+ // class C {
+ // static int method1() {
+ // return 1;
+ // }
+ // static int method2() {
+ // return 1;
+ // }
+ // }
+ //
+ public final void processMethodCode(
+ AppInfoWithSubtyping appInfo,
+ DexEncodedMethod method,
+ IRCode code,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ InlinerAction inliner) throws ApiLevelException {
+
+ // Collect all the new-instance instructions in the code before inlining.
+ List<NewInstance> newInstances = Streams.stream(code.instructionIterator())
+ .filter(Instruction::isNewInstance)
+ .map(Instruction::asNewInstance)
+ .collect(Collectors.toList());
+
+ nextNewInstance:
+ for (NewInstance newInstance : newInstances) {
+ Value eligibleInstance = newInstance.outValue();
+ if (eligibleInstance == null) {
+ continue;
+ }
+
+ DexType eligibleClass = newInstance.clazz;
+ if (!isClassEligible(appInfo, eligibleClass)) {
+ continue;
+ }
+
+ // No Phi users.
+ if (eligibleInstance.numberOfPhiUsers() > 0) {
+ continue;
+ }
+
+ Set<Instruction> uniqueUsers = eligibleInstance.uniqueUsers();
+
+ // Find an initializer invocation.
+ InvokeDirect eligibleInitCall = null;
+ Map<DexField, Integer> mappings = null;
+ for (Instruction user : uniqueUsers) {
+ if (!user.isInvokeDirect()) {
+ continue;
+ }
+
+ InvokeDirect candidate = user.asInvokeDirect();
+ DexMethod candidateInit = candidate.getInvokedMethod();
+ if (factory.isConstructor(candidateInit) &&
+ candidate.inValues().lastIndexOf(eligibleInstance) == 0) {
+
+ if (candidateInit.holder != eligibleClass) {
+ // Inlined constructor call? We won't get field initialization mapping in this
+ // case, but since we only support eligible classes extending java.lang.Object,
+ // it's safe to assume an empty mapping.
+ if (candidateInit.holder == factory.objectType) {
+ mappings = Collections.emptyMap();
+ }
+
+ } else {
+ // Is it a call to an *eligible* constructor?
+ mappings = getConstructorFieldMappings(appInfo, candidateInit, isProcessedConcurrently);
+ }
+
+ eligibleInitCall = candidate;
+ break;
+ }
+ }
+
+ if (mappings == null) {
+ continue;
+ }
+
+ // Check all regular users.
+ Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = new IdentityHashMap<>();
+
+ for (Instruction user : uniqueUsers) {
+ if (user == eligibleInitCall) {
+ continue /* next user */;
+ }
+
+ if (user.isInstanceGet()) {
+ InstanceGet instanceGet = user.asInstanceGet();
+ if (mappings.containsKey(instanceGet.getField())) {
+ continue /* next user */;
+ }
+
+ // Not replaceable field read.
+ continue nextNewInstance;
+ }
+
+ if (user.isInvokeVirtual() || user.isInvokeInterface()) {
+ InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+ if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
+ continue nextNewInstance; // Instance must only be passes as a receiver.
+ }
+
+ DexEncodedMethod singleTarget =
+ findSingleTarget(appInfo, invoke, eligibleClass);
+ if (singleTarget == null) {
+ continue nextNewInstance;
+ }
+ if (isProcessedConcurrently.test(singleTarget)) {
+ continue nextNewInstance;
+ }
+ if (method == singleTarget) {
+ continue nextNewInstance; // Don't inline itself.
+ }
+
+ if (!singleTarget.getOptimizationInfo()
+ .isReceiverOnlyUsedForReadingFields(mappings.keySet())) {
+ continue nextNewInstance; // Target must be trivial.
+ }
+
+ if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
+ // We won't be able to inline it here.
+
+ // Note that there may be some false negatives here since the method may
+ // reference private fields of its class which are supposed to be replaced
+ // with arguments after inlining. We should try and improve it later.
+
+ // Using -allowaccessmodification mitigates this.
+ continue nextNewInstance;
+ }
+
+ methodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass));
+ continue /* next user */;
+ }
+
+ continue nextNewInstance; // Unsupported user.
+ }
+
+ // Force-inline of method invocation if any.
+ inlineAllCalls(inliner, methodCalls);
+ assert assertOnlyConstructorAndFieldReads(eligibleInstance, eligibleInitCall, mappings);
+
+ // Replace all field reads with arguments passed to the constructor.
+ patchFieldReads(eligibleInstance, eligibleInitCall, mappings);
+ assert assertOnlyConstructor(eligibleInstance, eligibleInitCall);
+
+ // Remove constructor call and new-instance instructions.
+ removeInstruction(eligibleInitCall);
+ removeInstruction(newInstance);
+ code.removeAllTrivialPhis();
+ }
+ }
+
+ private void inlineAllCalls(InlinerAction inliner,
+ Map<InvokeMethodWithReceiver, InliningInfo> methodCalls) throws ApiLevelException {
+ if (!methodCalls.isEmpty()) {
+ inliner.inline(methodCalls);
+ }
+ }
+
+ private void patchFieldReads(
+ Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
+ for (Instruction user : instance.uniqueUsers()) {
+ if (!user.isInstanceGet()) {
+ continue;
+ }
+ InstanceGet fieldRead = user.asInstanceGet();
+
+ // Replace the field read with
+ assert mappings.containsKey(fieldRead.getField());
+ Value arg = invokeMethod.inValues().get(1 + mappings.get(fieldRead.getField()));
+ assert arg != null;
+ Value value = fieldRead.outValue();
+ if (value != null) {
+ value.replaceUsers(arg);
+ assert value.numberOfAllUsers() == 0;
+ }
+
+ // Remove instruction.
+ removeInstruction(fieldRead);
+ }
+ }
+
+ private void removeInstruction(Instruction instruction) {
+ instruction.inValues().forEach(v -> v.removeUser(instruction));
+ instruction.getBlock().removeInstruction(instruction);
+ }
+
+ private boolean assertOnlyConstructorAndFieldReads(
+ Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
+ for (Instruction user : instance.uniqueUsers()) {
+ if (user != invokeMethod &&
+ !(user.isInstanceGet() && mappings.containsKey(user.asFieldInstruction().getField()))) {
+ throw new Unreachable("Not all calls are inlined!");
+ }
+ }
+ return true;
+ }
+
+ private boolean assertOnlyConstructor(Value instance, InvokeDirect invokeMethod) {
+ for (Instruction user : instance.uniqueUsers()) {
+ if (user != invokeMethod) {
+ throw new Unreachable("Not all field reads are substituted!");
+ }
+ }
+ return true;
+ }
+
+ private DexEncodedMethod findSingleTarget(
+ AppInfo appInfo, InvokeMethodWithReceiver invoke, DexType instanceType) {
+
+ // We don't use computeSingleTarget(...) on invoke since it sometimes fails to
+ // find the single target, while this code may be more successful since we exactly
+ // know what is the actual type of the receiver.
+
+ // Note that we also intentionally limit ourselves to methods directly defined in
+ // the instance's class. This may be improved later.
+
+ DexClass clazz = appInfo.definitionFor(instanceType);
+ if (clazz != null) {
+ DexMethod callee = invoke.getInvokedMethod();
+ for (DexEncodedMethod candidate : clazz.virtualMethods()) {
+ if (candidate.method.name == callee.name && candidate.method.proto == callee.proto) {
+ return candidate;
+ }
+ }
+ }
+ return null;
+ }
+
+ private Map<DexField, Integer> getConstructorFieldMappings(
+ AppInfo appInfo, DexMethod init, Predicate<DexEncodedMethod> isProcessedConcurrently) {
+ assert isClassEligible(appInfo, init.holder);
+
+ DexEncodedMethod definition = appInfo.definitionFor(init);
+ if (definition == null) {
+ return NO_MAPPING;
+ }
+
+ if (isProcessedConcurrently.test(definition)) {
+ return NO_MAPPING;
+ }
+
+ if (definition.accessFlags.isAbstract() || definition.accessFlags.isNative()) {
+ return NO_MAPPING;
+ }
+
+ return definition.getOptimizationInfo().onlyInitializesFieldsWithNoOtherSideEffects();
+ }
+
+ private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+ Boolean eligible = knownClasses.get(clazz);
+ if (eligible == null) {
+ Boolean computed = computeClassEligible(appInfo, clazz);
+ Boolean existing = knownClasses.putIfAbsent(clazz, computed);
+ assert existing == null || existing == computed;
+ eligible = existing == null ? computed : existing;
+ }
+ return eligible;
+ }
+
+ // Class is eligible for this optimization. Eligibility implementation:
+ // - not an abstract or interface
+ // - directly extends java.lang.Object
+ // - does not declare finalizer
+ // - does not trigger any static initializers
+ private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
+ DexClass definition = appInfo.definitionFor(clazz);
+ if (definition == null || definition.isLibraryClass() ||
+ definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+ return false;
+ }
+
+ // Must directly extend Object.
+ if (definition.superType != factory.objectType) {
+ return false;
+ }
+
+ // Class must not define finalizer.
+ for (DexEncodedMethod method : definition.virtualMethods()) {
+ if (method.method.name == factory.finalizeMethodName &&
+ method.method.proto == factory.objectMethods.finalize.proto) {
+ return false;
+ }
+ }
+
+ // Check for static initializers in this class or any of interfaces it implements.
+ return !appInfo.canTriggerStaticInitializer(clazz);
+ }
+}
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 4c40d61..92c68aa 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -81,6 +81,7 @@
enableDevirtualization = false;
enableNonNullTracking = false;
enableInlining = false;
+ enableClassInlining = false;
enableSwitchMapRemoval = false;
outline.enabled = false;
enableValuePropagation = false;
@@ -97,6 +98,7 @@
public boolean enableDevirtualization = true;
public boolean enableNonNullTracking = true;
public boolean enableInlining = true;
+ public boolean enableClassInlining = true;
public int inliningInstructionLimit = 5;
public boolean enableSwitchMapRemoval = true;
public final OutlineOptions outline = new OutlineOptions();
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index ac0955e..b27569b 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -995,6 +995,14 @@
"625-checker-licm-regressions"
);
+ private static List<String> requireClassInliningToBeDisabled = ImmutableList.of(
+ // Test depends on exception produced for missing method or similar cases, but
+ // after class inlining removes class instantiations and references the exception
+ // is not produced.
+ "042-new-instance",
+ "075-verification-error"
+ );
+
private static List<String> hasMissingClasses = ImmutableList.of(
"091-override-package-private-method",
"003-omnibus-opcodes",
@@ -1085,6 +1093,8 @@
private final boolean outputMayDiffer;
// Whether to disable inlining
private final boolean disableInlining;
+ // Whether to disable class inlining
+ private final boolean disableClassInlining;
// Has missing classes.
private final boolean hasMissingClasses;
@@ -1102,6 +1112,7 @@
boolean expectedToFailWithX8,
boolean outputMayDiffer,
boolean disableInlining,
+ boolean disableClassInlining,
boolean hasMissingClasses,
DexVm dexVm) {
this.name = name;
@@ -1117,6 +1128,7 @@
this.expectedToFailWithX8 = expectedToFailWithX8;
this.outputMayDiffer = outputMayDiffer;
this.disableInlining = disableInlining;
+ this.disableClassInlining = disableClassInlining;
this.hasMissingClasses = hasMissingClasses;
}
@@ -1142,6 +1154,7 @@
false,
false,
disableInlining,
+ true, // Disable class inlining for JCTF tests.
false,
dexVm);
}
@@ -1306,6 +1319,7 @@
expectedToFailWithCompilerSet.contains(name),
outputMayDiffer.contains(name),
requireInliningToBeDisabled.contains(name),
+ requireClassInliningToBeDisabled.contains(name),
hasMissingClasses.contains(name),
dexVm));
}
@@ -1372,11 +1386,12 @@
String resultPath,
CompilationMode compilationMode,
boolean disableInlining,
+ boolean disableClassInlining,
boolean hasMissingClasses)
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
CompilationFailedException {
executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, compilationMode, null,
- disableInlining, hasMissingClasses);
+ disableInlining, disableClassInlining, hasMissingClasses);
}
private void executeCompilerUnderTest(
@@ -1385,7 +1400,9 @@
String resultPath,
CompilationMode mode,
String keepRulesFile,
- boolean disableInlining, boolean hasMissingClasses)
+ boolean disableInlining,
+ boolean disableClassInlining,
+ boolean hasMissingClasses)
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
CompilationFailedException {
assert mode != null;
@@ -1523,6 +1540,9 @@
if (disableInlining) {
options.enableInlining = false;
}
+ if (disableClassInlining) {
+ options.enableClassInlining = false;
+ }
options.lineNumberOptimization = LineNumberOptimization.OFF;
// Some tests actually rely on missing classes for what they test.
options.ignoreMissingClasses = hasMissingClasses;
@@ -1734,7 +1754,8 @@
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
CompilationFailedException {
executeCompilerUnderTest(compilerUnderTest, fileNames, resultDir.getAbsolutePath(), mode,
- specification.disableInlining, specification.hasMissingClasses);
+ specification.disableInlining, specification.disableClassInlining,
+ specification.hasMissingClasses);
if (!ToolHelper.artSupported()) {
return;
@@ -1907,7 +1928,8 @@
try {
executeCompilerUnderTest(
compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
- specification.disableInlining, specification.hasMissingClasses);
+ specification.disableInlining, specification.disableClassInlining,
+ specification.hasMissingClasses);
} catch (CompilationException | CompilationFailedException e) {
throw new CompilationError(e.getMessage(), e);
} catch (ExecutionException e) {
@@ -1919,13 +1941,15 @@
expectException(Throwable.class);
executeCompilerUnderTest(
compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
- specification.disableInlining, specification.hasMissingClasses);
+ specification.disableInlining, specification.disableClassInlining,
+ specification.hasMissingClasses);
System.err.println("Should have failed R8/D8 compilation with an exception.");
return;
} else {
executeCompilerUnderTest(
compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
- specification.disableInlining, specification.hasMissingClasses);
+ specification.disableInlining, specification.disableClassInlining,
+ specification.hasMissingClasses);
}
if (!specification.skipArt && ToolHelper.artSupported()) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index b973818..62dd4af 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -155,24 +155,27 @@
.build());
break;
}
- case R8:
- {
- R8Command command =
- addInputFile(R8Command.builder())
- .setOutput(getOutputFile(), outputMode)
- .setMode(mode)
- .build();
- ExceptionUtils.withR8CompilationHandler(
- command.getReporter(),
- () ->
- ToolHelper.runR8(
- command,
- options -> {
- options.lineNumberOptimization = LineNumberOptimization.OFF;
- options.enableCfFrontend = frontend == Frontend.CF;
- }));
- break;
- }
+ case R8: {
+ R8Command command =
+ addInputFile(R8Command.builder())
+ .setOutput(getOutputFile(), outputMode)
+ .setMode(mode)
+ .build();
+ ExceptionUtils.withR8CompilationHandler(
+ command.getReporter(),
+ () ->
+ ToolHelper.runR8(
+ command,
+ options -> {
+ options.lineNumberOptimization = LineNumberOptimization.OFF;
+ options.enableCfFrontend = frontend == Frontend.CF;
+ if (output == Output.CF) {
+ // Class inliner is not supported with CF backend yet.
+ options.enableClassInlining = false;
+ }
+ }));
+ break;
+ }
default:
throw new Unreachable();
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index e256ae4..c6a2bd3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -39,6 +39,7 @@
private void configure(InternalOptions options) {
options.enableClassMerging = true;
+ options.enableClassInlining = false;
}
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
index 1f7c990..be7ee92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
@@ -50,7 +50,7 @@
List<String> pgConfigs = ImmutableList.of(
"-keep class " + CLASS_NAME + " { *; }",
"-dontshrink");
- AndroidApp app = compileWithR8(builder, pgConfigs, null);
+ AndroidApp app = compileWithR8(builder, pgConfigs, o -> o.enableClassInlining = false);
DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
assertNotNull(method);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index e8f2a2a..47f0db3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -110,7 +110,7 @@
.addProguardConfigurationFiles(proguardConfig)
.setDisableMinification(true)
.build(),
- null);
+ o -> o.enableClassInlining = false);
return dexOutputDir.resolve("classes.dex");
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
new file mode 100644
index 0000000..e9319d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2018, 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.classinliner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClass;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface1;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface1Impl;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.ReferencedFields;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.TrivialTestClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.Sets;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class ClassInlinerTest extends TestBase {
+ @Test
+ public void testTrivial() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(TrivialTestClass.class),
+ ToolHelper.getClassAsBytes(TrivialTestClass.Inner.class),
+ ToolHelper.getClassAsBytes(ReferencedFields.class),
+ ToolHelper.getClassAsBytes(EmptyClass.class),
+ ToolHelper.getClassAsBytes(EmptyClassWithInitializer.class),
+ ToolHelper.getClassAsBytes(Iface1.class),
+ ToolHelper.getClassAsBytes(Iface1Impl.class),
+ ToolHelper.getClassAsBytes(Iface2.class),
+ ToolHelper.getClassAsBytes(Iface2Impl.class),
+ ToolHelper.getClassAsBytes(CycleReferenceAB.class),
+ ToolHelper.getClassAsBytes(CycleReferenceBA.class),
+ ToolHelper.getClassAsBytes(ClassWithFinal.class)
+ };
+ String main = TrivialTestClass.class.getCanonicalName();
+ ProcessResult javaOutput = runOnJava(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+
+ AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+
+ assertEquals(
+ Collections.singleton("java.lang.StringBuilder"),
+ collectNewInstanceTypes(clazz, "testInner"));
+
+ assertEquals(
+ Collections.emptySet(),
+ collectNewInstanceTypes(clazz, "testConstructorMapping1"));
+
+ assertEquals(
+ Collections.singleton(
+ "com.android.tools.r8.ir.optimize.classinliner.trivial.ReferencedFields"),
+ collectNewInstanceTypes(clazz, "testConstructorMapping2"));
+
+ assertEquals(
+ Collections.singleton("java.lang.StringBuilder"),
+ collectNewInstanceTypes(clazz, "testConstructorMapping3"));
+
+ assertEquals(
+ Collections.emptySet(),
+ collectNewInstanceTypes(clazz, "testEmptyClass"));
+
+ assertEquals(
+ Collections.singleton(
+ "com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer"),
+ collectNewInstanceTypes(clazz, "testEmptyClassWithInitializer"));
+
+ assertEquals(
+ Collections.singleton(
+ "com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal"),
+ collectNewInstanceTypes(clazz, "testClassWithFinalizer"));
+
+ assertEquals(
+ Collections.emptySet(),
+ collectNewInstanceTypes(clazz, "testCallOnIface1"));
+
+ assertEquals(
+ Collections.singleton(
+ "com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
+ collectNewInstanceTypes(clazz, "testCallOnIface2"));
+
+ assertEquals(
+ Sets.newHashSet(
+ "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB",
+ "java.lang.StringBuilder"),
+ collectNewInstanceTypes(clazz, "testCycles"));
+
+ assertEquals(
+ Sets.newHashSet("java.lang.StringBuilder",
+ "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
+ collectNewInstanceTypes(inspector.clazz(CycleReferenceAB.class), "foo", "int"));
+
+ assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
+ }
+
+ private Set<String> collectNewInstanceTypes(
+ ClassSubject clazz, String methodName, String... params) {
+ assertNotNull(clazz);
+ MethodSignature signature = new MethodSignature(methodName, "void", params);
+ DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+ return filterInstructionKind(code, NewInstance.class)
+ .map(insn -> ((NewInstance) insn).getType().toSourceString())
+ .collect(Collectors.toSet());
+ }
+
+ private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
+ String config = keepMainProguardConfiguration(mainClass) + "\n"
+ + "-dontobfuscate\n"
+ + "-allowaccessmodification";
+
+ AndroidApp compiled = compileWithR8(app, config, o -> o.enableClassInlining = true);
+
+ // Materialize file for execution.
+ Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
+ compiled.writeToZip(generatedDexFile, OutputMode.DexIndexed);
+
+ // Run with ART.
+ String artOutput = ToolHelper.runArtNoVerificationErrors(
+ generatedDexFile.toString(), mainClass.getCanonicalName());
+
+ // Compare with Java.
+ ToolHelper.ProcessResult javaResult = ToolHelper.runJava(
+ ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
+
+ if (javaResult.exitCode != 0) {
+ System.out.println(javaResult.stdout);
+ System.err.println(javaResult.stderr);
+ fail("JVM failed for: " + mainClass);
+ }
+ assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
+
+ return compiled;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java
new file mode 100644
index 0000000..7e0239c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class ClassWithFinal {
+ public String doNothing() {
+ return "nothing at all";
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ doNothing();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
new file mode 100644
index 0000000..d26641a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class CycleReferenceAB {
+ private String a;
+
+ public CycleReferenceAB(String a) {
+ this.a = a;
+ }
+
+ public void foo(int depth) {
+ CycleReferenceBA ba = new CycleReferenceBA("depth=" + depth);
+ System.out.println("CycleReferenceAB::foo(" + depth + ")");
+ if (depth > 0) {
+ ba.foo(depth - 1);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "CycleReferenceAB(" + a + ")";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
new file mode 100644
index 0000000..1c5d147
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class CycleReferenceBA {
+ private String a;
+
+ public CycleReferenceBA(String a) {
+ this.a = a;
+ }
+
+ public void foo(int depth) {
+ CycleReferenceAB ab = new CycleReferenceAB("depth=" + depth);
+ System.out.println("CycleReferenceBA::foo(" + depth + ")");
+ if (depth > 0) {
+ ab.foo(depth - 1);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "CycleReferenceBA(" + a + ")";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java
new file mode 100644
index 0000000..cedfd77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class EmptyClass {
+ public String doNothing() {
+ return "nothing at all";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java
new file mode 100644
index 0000000..9671ea5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class EmptyClassWithInitializer {
+ public String doNothing() {
+ return "nothing at all";
+ }
+
+ static {
+ System.out.println("EmptyClassWithInitializer.<clinit>()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java
new file mode 100644
index 0000000..8320ec9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public interface Iface1 {
+ void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java
new file mode 100644
index 0000000..e42a04b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class Iface1Impl implements Iface1 {
+ final String value;
+
+ public Iface1Impl(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public void foo() {
+ System.out.println(value);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java
new file mode 100644
index 0000000..93a3d5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public interface Iface2 extends Iface1 {
+ Integer CONSTANT = 123;
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java
new file mode 100644
index 0000000..17dd4c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class Iface2Impl implements Iface2 {
+ final String value;
+
+ public Iface2Impl(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public void foo() {
+ System.out.println(value);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java
new file mode 100644
index 0000000..90263d6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class ReferencedFields {
+ private String a;
+ private String b;
+
+ public ReferencedFields(String a, String b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public ReferencedFields(String a) {
+ this.a = a;
+ }
+
+ public String getConcat() {
+ return a + " " + b;
+ }
+
+ public String getA() {
+ return a;
+ }
+
+ public String getB() {
+ return b;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
new file mode 100644
index 0000000..7e3cba2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2018, 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.classinliner.trivial;
+
+public class TrivialTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ TrivialTestClass test = new TrivialTestClass();
+ test.testInner();
+ test.testConstructorMapping1();
+ test.testConstructorMapping2();
+ test.testConstructorMapping3();
+ test.testEmptyClass();
+ test.testEmptyClassWithInitializer();
+ test.testClassWithFinalizer();
+ test.testCallOnIface1();
+ test.testCallOnIface2();
+ test.testCycles();
+ }
+
+ private synchronized void testInner() {
+ Inner inner = new Inner("inner{", 123, next() + "}");
+ System.out.println(inner.toString() + " " + inner.getPrefix() + " = " + inner.prefix);
+ }
+
+ private synchronized void testConstructorMapping1() {
+ ReferencedFields o = new ReferencedFields(next());
+ System.out.println(o.getA());
+ }
+
+ private synchronized void testConstructorMapping2() {
+ ReferencedFields o = new ReferencedFields(next());
+ System.out.println(o.getB());
+ }
+
+ private synchronized void testConstructorMapping3() {
+ ReferencedFields o = new ReferencedFields(next(), next());
+ System.out.println(o.getA() + o.getB() + o.getConcat());
+ }
+
+ private synchronized void testEmptyClass() {
+ new EmptyClass();
+ }
+
+ private synchronized void testEmptyClassWithInitializer() {
+ new EmptyClassWithInitializer();
+ }
+
+ private synchronized void testClassWithFinalizer() {
+ new ClassWithFinal();
+ }
+
+ private void callOnIface1(Iface1 iface) {
+ iface.foo();
+ }
+
+ private synchronized void testCallOnIface1() {
+ callOnIface1(new Iface1Impl(next()));
+ }
+
+ private void callOnIface2(Iface2 iface) {
+ iface.foo();
+ }
+
+ private synchronized void testCallOnIface2() {
+ callOnIface2(new Iface2Impl(next()));
+ System.out.println(Iface2Impl.CONSTANT); // Keep constant referenced
+ }
+
+ private synchronized void testCycles() {
+ new CycleReferenceAB("first").foo(3);
+ new CycleReferenceBA("second").foo(4);
+ }
+
+ public class Inner {
+ private String prefix;
+ private String suffix;
+ private double id;
+
+ public Inner(String prefix, double id, String suffix) {
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return prefix + id + suffix;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public double getId() {
+ return id;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+ public void setId(double id) {
+ this.id = id;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 93f4249..bb83093 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.utils.DexInspector.FieldSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -34,6 +35,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import org.junit.Assume;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -183,13 +185,23 @@
+ "}";
}
- protected void runTest(String folder, String mainClass, AndroidAppInspector inspector)
- throws Exception {
- runTest(folder, mainClass, null, inspector);
+ protected void runTest(String folder, String mainClass,
+ AndroidAppInspector inspector) throws Exception {
+ runTest(folder, mainClass, null, null, inspector);
+ }
+
+ protected void runTest(String folder, String mainClass,
+ Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception {
+ runTest(folder, mainClass, null, optionsConsumer, inspector);
+ }
+
+ protected void runTest(String folder, String mainClass,
+ String extraProguardRules, AndroidAppInspector inspector) throws Exception {
+ runTest(folder, mainClass, extraProguardRules, null, inspector);
}
protected void runTest(String folder, String mainClass, String extraProguardRules,
- AndroidAppInspector inspector) throws Exception {
+ Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
String proguardRules = buildProguardRules(mainClass);
@@ -206,7 +218,7 @@
// Build with R8
AndroidApp.Builder builder = AndroidApp.builder();
builder.addProgramFiles(classpath);
- AndroidApp app = compileWithR8(builder.build(), proguardRules);
+ AndroidApp app = compileWithR8(builder.build(), proguardRules, optionsConsumer);
// Materialize file for execution.
Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index 315e980..ee9065f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -252,7 +252,7 @@
@Test
public void testTrivialKs() throws Exception {
final String mainClassName = "lambdas_kstyle_trivial.MainKt";
- runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
+ runTest("lambdas_kstyle_trivial", mainClassName, (app) -> {
Verifier verifier = new Verifier(app);
String pkg = "lambdas_kstyle_trivial";
@@ -293,7 +293,7 @@
@Test
public void testCapturesKs() throws Exception {
final String mainClassName = "lambdas_kstyle_captures.MainKt";
- runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
+ runTest("lambdas_kstyle_captures", mainClassName, (app) -> {
Verifier verifier = new Verifier(app);
String pkg = "lambdas_kstyle_captures";
String grpPkg = allowAccessModification ? "" : pkg;
@@ -318,7 +318,7 @@
@Test
public void testGenericsNoSignatureKs() throws Exception {
final String mainClassName = "lambdas_kstyle_generics.MainKt";
- runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
+ runTest("lambdas_kstyle_generics", mainClassName, (app) -> {
Verifier verifier = new Verifier(app);
String pkg = "lambdas_kstyle_generics";
String grpPkg = allowAccessModification ? "" : pkg;
@@ -387,7 +387,7 @@
@Test
public void testTrivialJs() throws Exception {
final String mainClassName = "lambdas_jstyle_trivial.MainKt";
- runTest("lambdas_jstyle_trivial", mainClassName, null, (app) -> {
+ runTest("lambdas_jstyle_trivial", mainClassName, (app) -> {
Verifier verifier = new Verifier(app);
String pkg = "lambdas_jstyle_trivial";
String grp = allowAccessModification ? "" : pkg;
@@ -435,7 +435,7 @@
@Test
public void testSingleton() throws Exception {
final String mainClassName = "lambdas_singleton.MainKt";
- runTest("lambdas_singleton", mainClassName, null, (app) -> {
+ runTest("lambdas_singleton", mainClassName, (app) -> {
Verifier verifier = new Verifier(app);
String pkg = "lambdas_singleton";
String grp = allowAccessModification ? "" : pkg;
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 73d70e1..f884376 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
import org.junit.Test;
public class R8KotlinPropertiesTest extends AbstractR8KotlinTestBase {
@@ -79,11 +81,13 @@
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
+ private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
+
@Test
public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -100,7 +104,7 @@
public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -122,7 +126,7 @@
public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useProtectedProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -145,7 +149,7 @@
public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -168,7 +172,7 @@
public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -191,7 +195,7 @@
public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -216,7 +220,7 @@
public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -233,7 +237,7 @@
public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -256,7 +260,7 @@
public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_useProtectedLateInitProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -277,7 +281,7 @@
public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -296,7 +300,7 @@
public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -315,7 +319,7 @@
public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -332,7 +336,7 @@
public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassExists(dexInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -358,7 +362,7 @@
public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassExists(dexInspector,
"properties.CompanionProperties");
@@ -389,7 +393,7 @@
public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassExists(dexInspector,
"properties.CompanionProperties");
@@ -423,7 +427,7 @@
public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassExists(dexInspector,
"properties.CompanionProperties");
@@ -454,7 +458,7 @@
public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassExists(dexInspector,
"properties.CompanionProperties");
@@ -486,7 +490,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
@@ -517,7 +521,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
@@ -541,7 +545,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 9026740..a185f4c 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -33,7 +33,7 @@
"-dontobfuscate"),
Origin.unknown());
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
DexInspector inspector = new DexInspector(app);
List<FoundClassSubject> classes = inspector.allClasses();
@@ -66,7 +66,7 @@
"-dontobfuscate"),
Origin.unknown());
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
DexInspector inspector = new DexInspector(app);
List<FoundClassSubject> classes = inspector.allClasses();
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 3de225d..5845dd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -39,7 +39,7 @@
"-dontobfuscate"),
Origin.unknown());
builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
inspection.accept(new DexInspector(app));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 1151648..91fc9d4 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -599,7 +599,7 @@
AndroidApp app;
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
try {
- app = ToolHelper.runR8(builder.build());
+ app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
} catch (CompilationError e) {
assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod));
return;
@@ -656,7 +656,7 @@
builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
Path proguardCompatibilityRules = temp.newFile().toPath();
builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
inspection.accept(new DexInspector(app));
// Check the Proguard compatibility configuration generated.
ProguardConfigurationParser parser =