Merge "Preserve input markers from dex inputs to D8."
diff --git a/build.gradle b/build.gradle
index 7761e37..abefa15 100644
--- a/build.gradle
+++ b/build.gradle
@@ -504,6 +504,7 @@
task.relocate('kotlin', 'com.android.tools.r8.jetbrains.kotlin')
task.relocate('kotlinx', 'com.android.tools.r8.jetbrains.kotlinx')
task.relocate('org.jetbrains', 'com.android.tools.r8.org.jetbrains')
+ task.relocate('org.intellij', 'com.android.tools.r8.org.intellij')
}
task repackageDeps(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index f896eee..00ce913 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.3.1-dev";
+ public static final String LABEL = "1.3.2-dev";
private Version() {
}
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 6cbad63..69a744f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -481,15 +481,15 @@
return result;
}
- public boolean canTriggerStaticInitializer(DexType type) {
+ public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
// Process superclass chain.
DexType clazz = type;
while (clazz != null && clazz != dexItemFactory.objectType) {
DexClass definition = definitionFor(clazz);
- if (canTriggerStaticInitializer(definition)) {
- return true; // Assume it *may* trigger if we didn't find the definition.
+ if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+ return true;
}
knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
clazz = definition.superType;
@@ -501,7 +501,7 @@
DexType iface = queue.remove();
DexClass definition = definitionFor(iface);
if (canTriggerStaticInitializer(definition)) {
- return true; // Assume it *may* trigger if we didn't find the definition.
+ return true;
}
if (!definition.isInterface()) {
throw new Unreachable(iface.toSourceString() + " is expected to be an interface");
@@ -517,6 +517,7 @@
}
private static boolean canTriggerStaticInitializer(DexClass clazz) {
+ // Assume it *may* trigger if we didn't find the definition.
return clazz == null || clazz.hasClassInitializer();
}
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 d234702..4281e80 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -590,6 +590,24 @@
}
}
+ public static class TrivialInitializer {
+ private TrivialInitializer() {
+ }
+
+ public static final class InstanceClassTrivialInitializer extends TrivialInitializer {
+ public static final InstanceClassTrivialInitializer INSTANCE =
+ new InstanceClassTrivialInitializer();
+ }
+
+ public static final class ClassTrivialInitializer extends TrivialInitializer {
+ public final DexField field;
+
+ public ClassTrivialInitializer(DexField field) {
+ this.field = field;
+ }
+ }
+ }
+
public static class OptimizationInfo {
private int returnedArgument = -1;
@@ -604,6 +622,7 @@
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
private ClassInlinerEligibility classInlinerEligibility = null;
+ private TrivialInitializer trivialInitializerInfo = null;
private OptimizationInfo() {
// Intentionally left empty.
@@ -648,6 +667,14 @@
return this.classInlinerEligibility;
}
+ private void setTrivialInitializer(TrivialInitializer info) {
+ this.trivialInitializerInfo = info;
+ }
+
+ public TrivialInitializer getTrivialInitializerInfo() {
+ return this.trivialInitializerInfo;
+ }
+
public long getReturnedConstant() {
assert returnsConstant();
return returnedConstant;
@@ -750,6 +777,10 @@
ensureMutableOI().setClassInlinerEligibility(eligibility);
}
+ synchronized public void setTrivialInitializer(TrivialInitializer info) {
+ ensureMutableOI().setTrivialInitializer(info);
+ }
+
synchronized public void markForceInline() {
ensureMutableOI().markForceInline();
}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 2c12a90..7d17f11 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -20,11 +20,35 @@
* <li>Renaming private methods/fields.</li>
* <li>Moving methods/fields to a super/subclass.</li>
* <li>Replacing method/field references by the same method/field on a super/subtype</li>
+ * <li>Moved methods might require changed invocation type at the call site</li>
* </ul>
* Note that the latter two have to take visibility into account.
*/
public abstract class GraphLense {
+ /**
+ * Result of a method lookup in a GraphLense.
+ *
+ * This provide the new target and the invoke type to use.
+ */
+ public static class GraphLenseLookupResult {
+ private final DexMethod method;
+ private final Type type;
+
+ public GraphLenseLookupResult(DexMethod method, Type type) {
+ this.method = method;
+ this.type = type;
+ }
+
+ public DexMethod getMethod() {
+ return method;
+ }
+
+ public Type getType() {
+ return type;
+ }
+ }
+
public static class Builder {
protected Builder() {
@@ -68,10 +92,11 @@
// This overload can be used when the graph lense is known to be context insensitive.
public DexMethod lookupMethod(DexMethod method) {
assert isContextFreeForMethod(method);
- return lookupMethod(method, null, null);
+ return lookupMethod(method, null, null).getMethod();
}
- public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type);
+ public abstract GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexEncodedMethod context, Type type);
// Context sensitive graph lenses should override this method.
public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
@@ -126,8 +151,9 @@
}
@Override
- public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
- return method;
+ public GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexEncodedMethod context, Type type) {
+ return new GraphLenseLookupResult(method, type);
}
@Override
@@ -141,19 +167,29 @@
}
}
+ /**
+ * GraphLense implementation with a parent lense using a simple mapping for type, method and
+ * field mapping.
+ *
+ * Subclasses can override the lookup methods.
+ *
+ * For method mapping where invocation type can change just override
+ * {@link #mapInvocationType(DexMethod, DexMethod, DexEncodedMethod, Type)} if
+ * the default name mapping applies, and only invocation type might need to change.
+ */
public static class NestedGraphLense extends GraphLense {
- private final GraphLense previousLense;
+ protected final GraphLense previousLense;
protected final DexItemFactory dexItemFactory;
- private final Map<DexType, DexType> typeMap;
+ protected final Map<DexType, DexType> typeMap;
private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>();
- private final Map<DexMethod, DexMethod> methodMap;
- private final Map<DexField, DexField> fieldMap;
+ protected final Map<DexMethod, DexMethod> methodMap;
+ protected final Map<DexField, DexField> fieldMap;
public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
- this.typeMap = typeMap;
+ this.typeMap = typeMap.isEmpty() ? null : typeMap;
this.methodMap = methodMap;
this.fieldMap = fieldMap;
this.previousLense = previousLense;
@@ -180,13 +216,58 @@
}
}
DexType previous = previousLense.lookupType(type);
- return typeMap.getOrDefault(previous, previous);
+ return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
}
@Override
- public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
- DexMethod previous = previousLense.lookupMethod(method, context, type);
- return methodMap.getOrDefault(previous, previous);
+ public GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexEncodedMethod context, Type type) {
+ GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+ DexMethod newMethod = methodMap.get(previous.getMethod());
+ if (newMethod == null) {
+ return previous;
+ }
+ // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
+ // that only subclasses which are known to need it actually do it?
+ return new GraphLenseLookupResult(
+ newMethod, mapInvocationType(newMethod, method, context, type));
+ }
+
+ /**
+ * Default invocation type mapping.
+ *
+ * This is an identity mapping. If a subclass need invocation type mapping either override
+ * this method or {@link #lookupMethod(DexMethod, DexEncodedMethod, Type)}
+ */
+ protected Type mapInvocationType(
+ DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+ return type;
+ }
+
+ /**
+ * Standard mapping between interface and virtual invoke type.
+ *
+ * Handle methods moved from interface to class or class to interface.
+ */
+ final protected Type mapVirtualInterfaceInvocationTypes(
+ AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod,
+ DexEncodedMethod context, Type type) {
+ if (type == Type.VIRTUAL || type == Type.INTERFACE) {
+ // Get the invoke type of the actual definition.
+ DexClass newTargetClass = appInfo.definitionFor(newMethod.holder);
+ if (newTargetClass == null) {
+ return type;
+ }
+ DexClass originalTargetClass = appInfo.definitionFor(originalMethod.holder);
+ if (originalTargetClass != null
+ && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
+ // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
+ // the IncompatibleClassChangeError the original invoke would have triggered.
+ return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
+ }
+ return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
+ }
+ return type;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 21b8dc4..85506ad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -363,7 +363,7 @@
private void processInvoke(Type type, DexMethod method) {
DexEncodedMethod source = caller.method;
- method = graphLense.lookupMethod(method, source, type);
+ method = graphLense.lookupMethod(method, source, type).getMethod();
DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
if (definition != null) {
assert !source.accessFlags.isBridge() || definition != caller.method;
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 7d7e5f3..1134686 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
@@ -141,7 +141,7 @@
this.memberValuePropagation =
options.enableValuePropagation ?
new MemberValuePropagation(appInfo.withLiveness()) : null;
- this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping(), options);
+ this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
if (appInfo.hasLiveness()) {
// When disabling the pruner here, also disable the ProtoLiteExtension in R8.java.
this.protoLiteRewriter =
@@ -725,17 +725,20 @@
interfaceMethodRewriter.rewriteMethodReferences(method, code);
assert code.isConsistentSSA();
}
- if (lambdaMerger != null) {
- lambdaMerger.processMethodCode(method, code);
+
+ if (classInliner != null) {
+ // Class inliner should work before lambda merger, so if it inlines the
+ // lambda, it is not get collected by merger.
+ assert options.enableInlining && inliner != null;
+ classInliner.processMethodCode(
+ appInfo.withLiveness(), method, code, isProcessedConcurrently,
+ methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline)
+ );
assert code.isConsistentSSA();
}
- if (classInliner != null) {
- assert options.enableInlining && inliner != null;
- classInliner.processMethodCode(
- appInfo.withSubtyping(), method, code, isProcessedConcurrently,
- methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline)
- );
+ if (lambdaMerger != null) {
+ lambdaMerger.processMethodCode(method, code);
assert code.isConsistentSSA();
}
@@ -760,6 +763,9 @@
// Analysis must be done after method is rewritten by logArgumentTypes()
codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
+ if (method.isInstanceInitializer() || method.isClassInitializer()) {
+ codeRewriter.identifyTrivialInitializer(method, code, feedback);
+ }
printMethod(code, "Optimized IR (SSA)");
finalizeIR(method, code, feedback);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 4c0be09..e653ff0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.ConstClass;
@@ -35,7 +36,6 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
import java.util.ListIterator;
import java.util.stream.Collectors;
@@ -44,13 +44,10 @@
private final GraphLense graphLense;
private final AppInfoWithSubtyping appInfo;
- private final InternalOptions options;
- public LensCodeRewriter(
- GraphLense graphLense, AppInfoWithSubtyping appInfo, InternalOptions options) {
+ public LensCodeRewriter(GraphLense graphLense, AppInfoWithSubtyping appInfo) {
this.graphLense = graphLense;
this.appInfo = appInfo;
- this.options = options;
}
private Value makeOutValue(Instruction insn, IRCode code) {
@@ -110,8 +107,10 @@
if (!invokedHolder.isClassType()) {
continue;
}
- DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method, invoke.getType());
- Invoke.Type invokeType = getInvokeType(invoke, actualTarget, invokedMethod, method);
+ GraphLenseLookupResult lenseLookup =
+ graphLense.lookupMethod(invokedMethod, method, invoke.getType());
+ DexMethod actualTarget = lenseLookup.getMethod();
+ Invoke.Type invokeType = lenseLookup.getType();
if (actualTarget != invokedMethod || invoke.getType() != invokeType) {
Invoke newInvoke = Invoke.create(invokeType, actualTarget, null,
invoke.outValue(), invoke.inValues());
@@ -230,16 +229,17 @@
DexEncodedMethod method, DexMethodHandle methodHandle) {
if (methodHandle.isMethodHandle()) {
DexMethod invokedMethod = methodHandle.asMethod();
- DexMethod actualTarget =
+ GraphLenseLookupResult lenseLookup =
graphLense.lookupMethod(invokedMethod, method, methodHandle.type.toInvokeType());
+ DexMethod actualTarget = lenseLookup.getMethod();
if (actualTarget != invokedMethod) {
- DexClass clazz = appInfo.definitionFor(actualTarget.holder);
MethodHandleType newType = methodHandle.type;
- if (clazz != null
- && (newType.isInvokeInterface() || newType.isInvokeInstance())) {
- newType = clazz.accessFlags.isInterface()
- ? MethodHandleType.INVOKE_INTERFACE
- : MethodHandleType.INVOKE_INSTANCE;
+ DexClass clazz = appInfo.definitionFor(actualTarget.holder);
+ if (clazz != null && (newType.isInvokeInterface() || newType.isInvokeInstance())) {
+ newType =
+ lenseLookup.getType() == Type.INTERFACE
+ ? MethodHandleType.INVOKE_INTERFACE
+ : MethodHandleType.INVOKE_INSTANCE;
}
return new DexMethodHandle(newType, actualTarget);
}
@@ -252,55 +252,4 @@
}
return methodHandle;
}
-
- private Type getInvokeType(
- InvokeMethod invoke,
- DexMethod actualTarget,
- DexMethod originalTarget,
- DexEncodedMethod invocationContext) {
- // We might move methods from interfaces to classes and vice versa. So we have to support
- // fixing the invoke kind, yet only if it was correct to start with.
- if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
- // Get the invoke type of the actual definition.
- DexClass newTargetClass = appInfo.definitionFor(actualTarget.holder);
- if (newTargetClass == null) {
- return invoke.getType();
- }
- DexClass originalTargetClass = appInfo.definitionFor(originalTarget.holder);
- if (originalTargetClass != null
- && (originalTargetClass.isInterface() ^ (invoke.getType() == Type.INTERFACE))) {
- // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
- // the IncompatibleClassChangeError the original invoke would have triggered.
- return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
- }
- return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
- }
- if (options.enableClassMerging && invoke.isInvokeSuper()) {
- if (actualTarget.getHolder() == invocationContext.method.getHolder()) {
- DexClass targetClass = appInfo.definitionFor(actualTarget.holder);
- if (targetClass == null) {
- return invoke.getType();
- }
-
- // If the super class A of the enclosing class B (i.e., invocationContext.method.holder)
- // has been merged into B during vertical class merging, and this invoke-super instruction
- // was resolving to a method in A, then the target method has been changed to a direct
- // method and moved into B, so that we need to use an invoke-direct instruction instead of
- // invoke-super.
- //
- // At this point, we have an invoke-super instruction where the static target is the
- // enclosing class. However, such an instruction could occur even if a subclass has never
- // been merged into the enclosing class. Therefore, to determine if vertical class merging
- // has been applied, we look if there is a direct method with the right signature, and only
- // return Type.DIRECT in that case.
- DexEncodedMethod method = targetClass.lookupDirectMethod(actualTarget);
- if (method != null) {
- // The target method has been moved from the super class into the sub class during class
- // merging such that we now need to use an invoke-direct instruction.
- return Type.DIRECT;
- }
- }
- }
- return invoke.getType();
- }
}
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 16a77c8..2f17a4c 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
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public interface OptimizationFeedback {
@@ -17,4 +18,5 @@
void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
+ void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info);
}
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 7303b6b..1026e9b 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
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class OptimizationFeedbackDirect implements OptimizationFeedback {
@@ -50,4 +51,9 @@
DexEncodedMethod method, ClassInlinerEligibility eligibility) {
method.setClassInlinerEligibility(eligibility);
}
+
+ @Override
+ public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+ method.setTrivialInitializer(info);
+ }
}
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 6915a2f..8fac02f 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
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class OptimizationFeedbackIgnore implements OptimizationFeedback {
@@ -35,4 +36,8 @@
public void setClassInlinerEligibility(
DexEncodedMethod method, ClassInlinerEligibility eligibility) {
}
+
+ @Override
+ public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+ }
}
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 e610d20..c3f02da 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
@@ -13,6 +13,9 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.ClassTrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.InstanceClassTrivialInitializer;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -48,6 +51,7 @@
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.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;
@@ -812,6 +816,187 @@
method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
}
+ public void identifyTrivialInitializer(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ boolean isInstanceInitializer = method.isInstanceInitializer();
+ boolean isClassInitializer = method.isClassInitializer();
+ assert isInstanceInitializer || isClassInitializer;
+ if (method.accessFlags.isNative()) {
+ return;
+ }
+
+ DexClass clazz = appInfo.definitionFor(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+
+ feedback.setTrivialInitializer(method,
+ isInstanceInitializer
+ ? computeInstanceInitializerInfo(code, clazz, appInfo::definitionFor)
+ : computeClassInitializerInfo(code, clazz));
+ }
+
+ // This method defines trivial instance initializer as follows:
+ //
+ // ** The initializer may only call the initializer of the base class, which
+ // itself must be trivial.
+ //
+ // ** java.lang.Object.<init>() is considered trivial.
+ //
+ // ** all arguments passed to a super-class initializer must be non-throwing
+ // constants or arguments.
+ //
+ // ** Assigns arguments or non-throwing constants to fields of this class.
+ //
+ // (Note that this initializer does not have to have zero arguments.)
+ private TrivialInitializer computeInstanceInitializerInfo(
+ IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
+ Value receiver = code.getThis();
+
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ Instruction insn = it.next();
+
+ if (insn.isReturn()) {
+ continue;
+ }
+
+ if (insn.isArgument()) {
+ continue;
+ }
+
+ if (insn.isConstInstruction()) {
+ if (insn.instructionInstanceCanThrow()) {
+ return null;
+ } else {
+ continue;
+ }
+ }
+
+ if (insn.isInvokeDirect()) {
+ InvokeDirect invokedDirect = insn.asInvokeDirect();
+ DexMethod invokedMethod = invokedDirect.getInvokedMethod();
+
+ if (invokedMethod.holder != clazz.superType) {
+ return null;
+ }
+
+ // java.lang.Object.<init>() is considered trivial.
+ if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+ continue;
+ }
+
+ DexClass holder = typeToClass.apply(invokedMethod.holder);
+ if (holder == null) {
+ return null;
+ }
+
+ DexEncodedMethod callTarget = holder.lookupDirectMethod(invokedMethod);
+ if (callTarget == null ||
+ !callTarget.isInstanceInitializer() ||
+ callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null ||
+ invokedDirect.getReceiver() != receiver) {
+ return null;
+ }
+
+ for (Value value : invokedDirect.inValues()) {
+ if (value != receiver && !(value.isConstant() || value.isArgument())) {
+ return null;
+ }
+ }
+ continue;
+ }
+
+ if (insn.isInstancePut()) {
+ InstancePut instancePut = insn.asInstancePut();
+ DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
+ if (field == null ||
+ instancePut.object() != receiver ||
+ (instancePut.value() != receiver && !instancePut.value().isArgument())) {
+ return null;
+ }
+ continue;
+ }
+
+ // Other instructions make the instance initializer not eligible.
+ return null;
+ }
+
+ return InstanceClassTrivialInitializer.INSTANCE;
+ }
+
+ // This method defines trivial class initializer as follows:
+ //
+ // ** The initializer may only instantiate an instance of the same class,
+ // initialize it with a call to a trivial constructor *without* arguments,
+ // and assign this instance to a static final field of the same class.
+ //
+ private synchronized TrivialInitializer computeClassInitializerInfo(IRCode code, DexClass clazz) {
+ InstructionIterator it = code.instructionIterator();
+
+ Value createdSingletonInstance = null;
+ DexField singletonField = null;
+
+ while (it.hasNext()) {
+ Instruction insn = it.next();
+
+ if (insn.isReturn()) {
+ continue;
+ }
+
+ if (insn.isNewInstance()) {
+ NewInstance newInstance = insn.asNewInstance();
+ if (createdSingletonInstance != null ||
+ newInstance.clazz != clazz.type ||
+ insn.outValue() == null) {
+ return null;
+ }
+ createdSingletonInstance = insn.outValue();
+ continue;
+ }
+
+ if (insn.isInvokeDirect()) {
+ InvokeDirect invokedDirect = insn.asInvokeDirect();
+ if (createdSingletonInstance == null ||
+ invokedDirect.getReceiver() != createdSingletonInstance) {
+ return null;
+ }
+
+ DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
+ if (callTarget == null ||
+ !callTarget.isInstanceInitializer() ||
+ !callTarget.method.proto.parameters.isEmpty() ||
+ callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null) {
+ return null;
+ }
+ continue;
+ }
+
+ if (insn.isStaticPut()) {
+ StaticPut staticPut = insn.asStaticPut();
+ if (singletonField != null ||
+ createdSingletonInstance == null ||
+ staticPut.inValue() != createdSingletonInstance) {
+ return null;
+ }
+
+ DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
+ if (field == null ||
+ !field.accessFlags.isStatic() ||
+ !field.accessFlags.isFinal()) {
+ return null;
+ }
+ singletonField = field.field;
+ continue;
+ }
+
+ // Other instructions make the class initializer not eligible.
+ return null;
+ }
+
+ return singletonField == null ? null : new ClassTrivialInitializer(singletonField);
+ }
+
/**
* 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/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 029b169..25cd969 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
@@ -271,7 +271,7 @@
Origin origin = appInfo.originFor(target.method.holder);
IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
if (!target.isProcessed()) {
- new LensCodeRewriter(graphLense, appInfo, options).rewrite(code, target);
+ new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
}
return code;
}
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
index d7a57df..e6b61f8 100644
--- 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
@@ -10,6 +10,8 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.ClassTrivialInitializer;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -22,12 +24,13 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
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.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.Streams;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@@ -55,11 +58,12 @@
// 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 may add new 'new-instance' instructions.
- // Processing them as well is possible, but does not seem to bring much value.
+ // - collect all 'new-instance' and 'static-get' instructions (called roots below) in
+ // the original code. Note that class inlining, if happens, mutates code and may add
+ // new root instructions. Processing them as well is possible, but does not seem to
+ // bring much value.
//
- // - for each 'new-instance' we check if it is eligible for inlining, i.e:
+ // - for each 'new-instance' root 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 comments in
// CodeRewriter::identifyClassInlinerEligibility(...))
@@ -71,14 +75,20 @@
// NOTE: if method receiver is used as a return value, the method call
// should ignore return value
//
- // - inline eligible 'new-instance' instructions, i.e:
+ // - for each 'static-get' root we check if it is eligible for inlining, i.e:
+ // -> the class of the new instance is 'eligible' (see computeClassEligible(...))
+ // *and* has a trivial class constructor (see CoreRewriter::computeClassInitializerInfo
+ // and description in isClassAndUsageEligible(...)) initializing the field root reads
+ // -> has only 'eligible' uses, (see above)
+ //
+ // - inline eligible root instructions, i.e:
// -> force inline methods called on the instance (including the initializer);
// (this may introduce additional instance field reads/writes on the receiver)
// -> replace instance field reads with appropriate values calculated based on
// fields writes
- // -> remove the call to superclass initializer
+ // -> remove the call to superclass initializer (if root is 'new-instance')
// -> remove all field writes
- // -> remove 'new-instance' instructions
+ // -> remove root instructions
//
// For example:
//
@@ -93,12 +103,21 @@
// return x;
// }
// }
+ // static class F {
+ // final static F I = new F();
+ // int getX() {
+ // return 123;
+ // }
+ // }
// static int method1() {
// return new L(1).x;
// }
// static int method2() {
// return new L(1).getX();
// }
+ // static int method3() {
+ // return F.I.getX();
+ // }
// }
//
// Code after class C is 'inlined':
@@ -109,47 +128,59 @@
// static int method2() {
// return 1;
// }
+ // static int method3() {
+ // return "F::getX";
+ // }
// }
//
public final void processMethodCode(
- AppInfoWithSubtyping appInfo,
+ AppInfoWithLiveness appInfo,
DexEncodedMethod method,
IRCode code,
Predicate<DexEncodedMethod> isProcessedConcurrently,
InlinerAction inliner) {
- // 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 all the new-instance and static-get instructions in the code before inlining.
+ List<Instruction> roots = Streams.stream(code.instructionIterator())
+ .filter(insn -> insn.isNewInstance() || insn.isStaticGet())
.collect(Collectors.toList());
- for (NewInstance newInstance : newInstances) {
- Value eligibleInstance = newInstance.outValue();
+ for (Instruction root : roots) {
+ Value eligibleInstance = root.outValue();
if (eligibleInstance == null) {
continue;
}
- DexType eligibleClass = newInstance.clazz;
- if (!isClassEligible(appInfo, eligibleClass)) {
+ DexType eligibleClass = root.isNewInstance()
+ ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+ DexClass eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+ if (eligibleClassDefinition == null) {
continue;
}
- Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = checkInstanceUsersEligibility(
- appInfo, method, isProcessedConcurrently, newInstance, eligibleInstance, eligibleClass);
+ // Check the static initializer of the type.
+ if (!isClassAndUsageEligible(root, eligibleClass,
+ eligibleClassDefinition, isProcessedConcurrently, appInfo)) {
+ continue;
+ }
+
+ Map<InvokeMethodWithReceiver, InliningInfo> methodCalls =
+ checkInstanceUsersEligibility(appInfo, method, isProcessedConcurrently,
+ root, eligibleInstance, eligibleClass, eligibleClassDefinition);
if (methodCalls == null) {
continue;
}
- if (getTotalEstimatedMethodSize(methodCalls) >= totalMethodInstructionLimit) {
+ if (!instructionLimitExempt(eligibleClassDefinition, appInfo) &&
+ getTotalEstimatedMethodSize(methodCalls) >= totalMethodInstructionLimit) {
continue;
}
// Inline the class instance.
forceInlineAllMethodInvocations(inliner, methodCalls);
- removeSuperClassInitializerAndFieldReads(code, newInstance, eligibleInstance);
+ removeSuperClassInitializerAndFieldReads(code, root, eligibleInstance);
removeFieldWrites(eligibleInstance, eligibleClass);
- removeInstruction(newInstance);
+ removeInstruction(root);
// Restore normality.
code.removeAllTrivialPhis();
@@ -157,10 +188,96 @@
}
}
+ private boolean isClassAndUsageEligible(Instruction root,
+ DexType eligibleClass, DexClass eligibleClassDefinition,
+ Predicate<DexEncodedMethod> isProcessedConcurrently, AppInfoWithLiveness appInfo) {
+ if (!isClassEligible(appInfo, eligibleClass)) {
+ return false;
+ }
+
+ if (root.isNewInstance()) {
+ // There must be no static initializer on the class itself.
+ return !eligibleClassDefinition.hasClassInitializer();
+ }
+
+ assert root.isStaticGet();
+
+ // Checking if we can safely inline class implemented following singleton-like
+ // pattern, by which we assume a static final field holding on to the reference
+ // initialized in class constructor.
+ //
+ // In general we are targeting cases when the class is defined as:
+ //
+ // class X {
+ // static final X F;
+ // static {
+ // F = new X();
+ // }
+ // }
+ //
+ // and being used as follows:
+ //
+ // void foo() {
+ // f = X.F;
+ // f.bar();
+ // }
+ //
+ // The main difference from the similar case of class inliner with 'new-instance'
+ // instruction is that in this case the instance we inline is not just leaked, but
+ // is actually published via X.F field. There are several risks we need to address
+ // in this case:
+ //
+ // Risk: instance stored in field X.F has changed after it was initialized in
+ // class initializer
+ // Solution: we assume that final field X.F is not modified outside the class
+ // initializer. In rare cases when it is (e.g. via reflections) it should
+ // be marked with keep rules
+ //
+ // Risk: instance stored in field X.F is not initialized yet
+ // Solution: not initialized instance can only be visible if X.<clinit>
+ // triggers other class initialization which references X.F. This
+ // situation should never happen if we:
+ // -- don't allow any superclasses to have static initializer,
+ // -- don't allow any subclasses,
+ // -- guarantee the class has trivial class initializer
+ // (see CodeRewriter::computeClassInitializerInfo), and
+ // -- guarantee the instance is initialized with trivial instance
+ // initializer (see CodeRewriter::computeInstanceInitializerInfo)
+ //
+ // Risk: instance stored in field X.F was mutated
+ // Solution: we require that class X does not have any instance fields, and
+ // if any of its superclasses has instance fields, accessing them will make
+ // this instance not eligible for inlining. I.e. even though the instance is
+ // publicized and its state has been mutated, it will not effect the logic
+ // of class inlining
+ //
+
+ if (eligibleClassDefinition.instanceFields().length > 0 ||
+ !eligibleClassDefinition.accessFlags.isFinal()) {
+ return false;
+ }
+
+ // Singleton instance must be initialized in class constructor.
+ DexEncodedMethod classInitializer = eligibleClassDefinition.getClassInitializer();
+ if (classInitializer == null || isProcessedConcurrently.test(classInitializer)) {
+ return false;
+ }
+
+ TrivialInitializer info =
+ classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+ assert info == null || info instanceof ClassTrivialInitializer;
+ DexField instanceField = root.asStaticGet().getField();
+ // Singleton instance field must NOT be pinned.
+ return info != null &&
+ ((ClassTrivialInitializer) info).field == instanceField &&
+ !appInfo.isPinned(eligibleClassDefinition.lookupStaticField(instanceField).field);
+ }
+
private Map<InvokeMethodWithReceiver, InliningInfo> checkInstanceUsersEligibility(
AppInfoWithSubtyping appInfo, DexEncodedMethod method,
Predicate<DexEncodedMethod> isProcessedConcurrently,
- NewInstance newInstanceInsn, Value receiver, DexType clazz) {
+ Instruction root, Value receiver,
+ DexType eligibleClass, DexClass eligibleClassDefinition) {
// No Phi users.
if (receiver.numberOfPhiUsers() > 0) {
@@ -169,14 +286,13 @@
Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = new IdentityHashMap<>();
- DexClass definition = appInfo.definitionFor(clazz);
-
for (Instruction user : receiver.uniqueUsers()) {
// Field read/write.
if (user.isInstanceGet() ||
(user.isInstancePut() && user.asInstancePut().value() != receiver)) {
DexField field = user.asFieldInstruction().getField();
- if (field.clazz == newInstanceInsn.clazz && definition.lookupInstanceField(field) != null) {
+ if (field.clazz == eligibleClass &&
+ eligibleClassDefinition.lookupInstanceField(field) != null) {
// Since class inliner currently only supports classes directly extending
// java.lang.Object, we don't need to worry about fields defined in superclasses.
continue;
@@ -184,10 +300,10 @@
return null; // Not eligible.
}
- // Eligible constructor call.
- if (user.isInvokeDirect()) {
+ // Eligible constructor call (for new instance roots only).
+ if (user.isInvokeDirect() && root.isNewInstance()) {
InliningInfo inliningInfo = isEligibleConstructorCall(appInfo, method,
- user.asInvokeDirect(), receiver, clazz, isProcessedConcurrently);
+ user.asInvokeDirect(), receiver, eligibleClass, isProcessedConcurrently);
if (inliningInfo != null) {
methodCalls.put(user.asInvokeDirect(), inliningInfo);
continue;
@@ -199,7 +315,7 @@
if (user.isInvokeVirtual() || user.isInvokeInterface()) {
InliningInfo inliningInfo = isEligibleMethodCall(
appInfo, method, user.asInvokeMethodWithReceiver(),
- receiver, clazz, isProcessedConcurrently);
+ receiver, eligibleClass, isProcessedConcurrently);
if (inliningInfo != null) {
methodCalls.put(user.asInvokeMethodWithReceiver(), inliningInfo);
continue;
@@ -215,11 +331,12 @@
// Remove call to superclass initializer, replace field reads with appropriate
// values, insert phis when needed.
private void removeSuperClassInitializerAndFieldReads(
- IRCode code, NewInstance newInstance, Value eligibleInstance) {
+ IRCode code, Instruction root, Value eligibleInstance) {
Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
for (Instruction user : eligibleInstance.uniqueUsers()) {
// Remove the call to superclass constructor.
- if (user.isInvokeDirect() &&
+ if (root.isNewInstance() &&
+ user.isInvokeDirect() &&
user.asInvokeDirect().getInvokedMethod() == factory.objectMethods.constructor) {
removeInstruction(user);
continue;
@@ -227,7 +344,7 @@
if (user.isInstanceGet()) {
// Replace a field read with appropriate value.
- replaceFieldRead(code, newInstance, user.asInstanceGet(), fieldHelpers);
+ replaceFieldRead(code, root, user.asInstanceGet(), fieldHelpers);
continue;
}
@@ -261,13 +378,24 @@
return totalSize;
}
- private void replaceFieldRead(IRCode code, NewInstance newInstance,
+ private boolean instructionLimitExempt(
+ DexClass eligibleClassDefinition, AppInfoWithLiveness appInfo) {
+ if (appInfo.isPinned(eligibleClassDefinition.type)) {
+ return false;
+ }
+ KotlinInfo kotlinInfo = eligibleClassDefinition.getKotlinInfo();
+ return kotlinInfo != null &&
+ kotlinInfo.isSyntheticClass() &&
+ kotlinInfo.asSyntheticClass().isLambda();
+ }
+
+ private void replaceFieldRead(IRCode code, Instruction root,
InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
Value value = fieldRead.outValue();
if (value != null) {
FieldValueHelper helper = fieldHelpers.computeIfAbsent(
- fieldRead.getField(), field -> new FieldValueHelper(field, code, newInstance));
+ fieldRead.getField(), field -> new FieldValueHelper(field, code, root));
Value newValue = helper.getValueForFieldRead(fieldRead.getBlock(), fieldRead);
value.replaceUsers(newValue);
for (FieldValueHelper fieldValueHelper : fieldHelpers.values()) {
@@ -282,16 +410,16 @@
private static final class FieldValueHelper {
final DexField field;
final IRCode code;
- final NewInstance newInstance;
+ final Instruction root;
private Value defaultValue = null;
private final Map<BasicBlock, Value> ins = new IdentityHashMap<>();
private final Map<BasicBlock, Value> outs = new IdentityHashMap<>();
- private FieldValueHelper(DexField field, IRCode code, NewInstance newInstance) {
+ private FieldValueHelper(DexField field, IRCode code, Instruction root) {
this.field = field;
this.code = code;
- this.newInstance = newInstance;
+ this.root = root;
}
void replaceValue(Value oldValue, Value newValue) {
@@ -370,10 +498,10 @@
Instruction instruction = iterator.previous();
assert instruction != null;
- if (instruction == newInstance ||
+ if (instruction == root ||
(instruction.isInstancePut() &&
instruction.asInstancePut().getField() == field &&
- instruction.asInstancePut().object() == newInstance.outValue())) {
+ instruction.asInstancePut().object() == root.outValue())) {
valueProducingInsn = instruction;
break;
}
@@ -386,14 +514,14 @@
return valueProducingInsn.asInstancePut().value();
}
- assert newInstance == valueProducingInsn;
+ assert root == valueProducingInsn;
if (defaultValue == null) {
// If we met newInstance it means that default value is supposed to be used.
defaultValue = code.createValue(ValueType.fromDexType(field.type));
ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0);
- defaultValueInsn.setPosition(newInstance.getPosition());
+ defaultValueInsn.setPosition(root.getPosition());
LinkedList<Instruction> instructions = block.getInstructions();
- instructions.add(instructions.indexOf(newInstance) + 1, defaultValueInsn);
+ instructions.add(instructions.indexOf(root) + 1, defaultValueInsn);
defaultValueInsn.setBlock(block);
}
return defaultValue;
@@ -539,7 +667,7 @@
// - is not an abstract class or interface
// - directly extends java.lang.Object
// - does not declare finalizer
- // - does not trigger any static initializers
+ // - does not trigger any static initializers except for its own
private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
DexClass definition = appInfo.definitionFor(clazz);
if (definition == null || definition.isLibraryClass() ||
@@ -561,6 +689,6 @@
}
// Check for static initializers in this class or any of interfaces it implements.
- return !appInfo.canTriggerStaticInitializer(clazz);
+ return !appInfo.canTriggerStaticInitializer(clazz, true);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 56089f4..492bcd0 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -46,14 +46,14 @@
assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
if (kind == InvokeKind.STATIC) {
assert method.accessFlags.isStatic();
- DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC);
+ DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC).getMethod();
DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(actualTarget);
if (targetMethod != null) {
addForwarding(method, targetMethod);
}
} else if (kind == InvokeKind.VIRTUAL) {
// TODO(herhut): Add support for bridges with multiple targets.
- DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL);
+ DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL).getMethod();
DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(actualTarget);
if (targetMethod != null) {
addForwarding(method, targetMethod);
@@ -93,15 +93,16 @@
}
@Override
- public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
- DexMethod previous = previousLense.lookupMethod(method, context, type);
- DexMethod bridge = bridgeTargetToBridgeMap.get(previous);
+ public GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexEncodedMethod context, Type type) {
+ GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+ DexMethod bridge = bridgeTargetToBridgeMap.get(previous.getMethod());
// Do not forward calls from a bridge method to itself while the bridge method is still
// a bridge.
if (bridge == null || (context.accessFlags.isBridge() && bridge == context.method)) {
return previous;
}
- return bridge;
+ return new GraphLenseLookupResult(bridge, type);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index fc408e3..da7a787 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -26,12 +26,13 @@
private final AppInfoWithLiveness appInfo;
private final GraphLense lense;
- private final GraphLense.Builder builder = GraphLense.builder();
+ private final MemberRebindingLense.Builder builder;
public MemberRebindingAnalysis(AppInfoWithLiveness appInfo, GraphLense lense) {
assert lense.isContextFreeForMethods();
this.appInfo = appInfo;
this.lense = lense;
+ this.builder = MemberRebindingLense.builder(appInfo);
}
private DexMethod validTargetFor(DexMethod target, DexMethod original) {
@@ -250,6 +251,6 @@
mergeFieldAccessContexts(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
appInfo::resolveFieldOn, DexClass::lookupField);
- return builder.build(appInfo.dexItemFactory, lense);
+ return builder.build(lense);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
new file mode 100644
index 0000000..4ee0cee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -0,0 +1,56 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class MemberRebindingLense extends NestedGraphLense {
+ public static class Builder extends NestedGraphLense.Builder {
+ private final AppInfo appInfo;
+
+ protected Builder(AppInfo appInfo) {
+ this.appInfo = appInfo;
+ }
+
+ public GraphLense build(GraphLense previousLense) {
+ assert typeMap.isEmpty();
+ if (methodMap.isEmpty() && fieldMap.isEmpty()) {
+ return previousLense;
+ }
+ return new MemberRebindingLense(appInfo, methodMap, fieldMap, previousLense);
+ }
+ }
+
+ private final AppInfo appInfo;
+
+ public MemberRebindingLense(
+ AppInfo appInfo,
+ Map<DexMethod, DexMethod> methodMap,
+ Map<DexField, DexField> fieldMap,
+ GraphLense previousLense) {
+ super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+ this.appInfo = appInfo;
+ }
+
+ public static Builder builder(AppInfo appInfo) {
+ return new Builder(appInfo);
+ }
+
+
+ @Override
+ protected Type mapInvocationType(
+ DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+ return super.mapVirtualInterfaceInvocationTypes(
+ appInfo, newMethod, originalMethod, context, type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index b11e33d..f0519a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -321,7 +321,7 @@
// The resulting graph lense that should be used after class merging.
VerticalClassMergerGraphLense.Builder renamedMembersLense =
- new VerticalClassMergerGraphLense.Builder();
+ VerticalClassMergerGraphLense.builder(appInfo);
Iterator<DexProgramClass> classIterator = classes.iterator();
@@ -490,7 +490,7 @@
private final DexClass source;
private final DexClass target;
private final VerticalClassMergerGraphLense.Builder deferredRenamings =
- new VerticalClassMergerGraphLense.Builder();
+ VerticalClassMergerGraphLense.builder(appInfo);
private boolean abortMerge = false;
private ClassMerger(DexClass source, DexClass target) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 942a857..99fb63a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -4,11 +4,13 @@
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -40,47 +42,57 @@
// invocation will hit the same implementation as the original super.m() call.
//
// For the invocation "invoke-virtual A.m()" in B.m2, this graph lense will return the method B.m.
-public class VerticalClassMergerGraphLense extends GraphLense {
- private final GraphLense previousLense;
+public class VerticalClassMergerGraphLense extends NestedGraphLense {
+ private final AppInfo appInfo;
- private final Map<DexField, DexField> fieldMap;
- private final Map<DexMethod, DexMethod> methodMap;
private final Set<DexMethod> mergedMethods;
private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps;
public VerticalClassMergerGraphLense(
+ AppInfo appInfo,
Map<DexField, DexField> fieldMap,
Map<DexMethod, DexMethod> methodMap,
Set<DexMethod> mergedMethods,
Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps,
GraphLense previousLense) {
- this.previousLense = previousLense;
- this.fieldMap = fieldMap;
- this.methodMap = methodMap;
+ super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+ this.appInfo = appInfo;
this.mergedMethods = mergedMethods;
this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
}
- @Override
- public DexType lookupType(DexType type) {
- return previousLense.lookupType(type);
+ public static Builder builder(AppInfo appInfo) {
+ return new Builder(appInfo);
}
@Override
- public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+ public GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexEncodedMethod context, Type type) {
assert isContextFreeForMethod(method) || (context != null && type != null);
- DexMethod previous = previousLense.lookupMethod(method, context, type);
- if (type == Type.SUPER && !mergedMethods.contains(context.method)) {
+ GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+ if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
Map<DexMethod, DexMethod> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.get(context.method.holder);
if (virtualToDirectMethodMap != null) {
- DexMethod directMethod = virtualToDirectMethodMap.get(previous);
+ DexMethod directMethod = virtualToDirectMethodMap.get(previous.getMethod());
if (directMethod != null) {
- return directMethod;
+ // If the super class A of the enclosing class B (i.e., context.method.holder)
+ // has been merged into B during vertical class merging, and this invoke-super instruction
+ // was resolving to a method in A, then the target method has been changed to a direct
+ // method and moved into B, so that we need to use an invoke-direct instruction instead of
+ // invoke-super.
+ return new GraphLenseLookupResult(directMethod, Type.DIRECT);
}
}
}
- return methodMap.getOrDefault(previous, previous);
+ return super.lookupMethod(previous.getMethod(), context, previous.getType());
+ }
+
+ @Override
+ protected Type mapInvocationType(
+ DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+ return super.mapVirtualInterfaceInvocationTypes(
+ appInfo, newMethod, originalMethod, context, type);
}
@Override
@@ -100,12 +112,6 @@
}
@Override
- public DexField lookupField(DexField field) {
- DexField previous = previousLense.lookupField(field);
- return fieldMap.getOrDefault(previous, previous);
- }
-
- @Override
public boolean isContextFreeForMethods() {
return contextualVirtualToDirectMethodMaps.isEmpty() && previousLense.isContextFreeForMethods();
}
@@ -126,6 +132,7 @@
}
public static class Builder {
+ private final AppInfo appInfo;
private final ImmutableMap.Builder<DexField, DexField> fieldMapBuilder = ImmutableMap.builder();
private final ImmutableMap.Builder<DexMethod, DexMethod> methodMapBuilder =
@@ -134,7 +141,9 @@
private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
new HashMap<>();
- public Builder() {}
+ private Builder(AppInfo appInfo) {
+ this.appInfo = appInfo;
+ }
public GraphLense build(GraphLense previousLense) {
Map<DexField, DexField> fieldMap = fieldMapBuilder.build();
@@ -145,6 +154,7 @@
return previousLense;
}
return new VerticalClassMergerGraphLense(
+ appInfo,
fieldMap,
methodMap,
mergedMethodsBuilder.build(),
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
index 38768cd..51a5262 100644
--- 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
@@ -16,13 +16,17 @@
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.Instruction;
import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.code.Sget;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
import com.android.tools.r8.ir.optimize.classinliner.builders.ControlFlow;
import com.android.tools.r8.ir.optimize.classinliner.builders.Pair;
import com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder;
import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
+import com.android.tools.r8.ir.optimize.classinliner.code.C;
+import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
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;
@@ -46,6 +50,7 @@
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,64 +72,64 @@
ToolHelper.getClassAsBytes(CycleReferenceBA.class),
ToolHelper.getClassAsBytes(ClassWithFinal.class)
};
- String main = TrivialTestClass.class.getCanonicalName();
- ProcessResult javaOutput = runOnJavaRaw(main, classes);
- assertEquals(0, javaOutput.exitCode);
-
AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+ String javaOutput = runOnJava(TrivialTestClass.class);
+ String artOutput = runOnArt(app, TrivialTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
DexInspector inspector = new DexInspector(app);
ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
- collectNewInstanceTypes(clazz, "testInner"));
+ collectTypes(clazz, "testInner", "void"));
assertEquals(
Collections.emptySet(),
- collectNewInstanceTypes(clazz, "testConstructorMapping1"));
+ collectTypes(clazz, "testConstructorMapping1", "void"));
assertEquals(
Collections.emptySet(),
- collectNewInstanceTypes(clazz, "testConstructorMapping2"));
+ collectTypes(clazz, "testConstructorMapping2", "void"));
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
- collectNewInstanceTypes(clazz, "testConstructorMapping3"));
+ collectTypes(clazz, "testConstructorMapping3", "void"));
assertEquals(
Collections.emptySet(),
- collectNewInstanceTypes(clazz, "testEmptyClass"));
+ collectTypes(clazz, "testEmptyClass", "void"));
assertEquals(
Collections.singleton(
"com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer"),
- collectNewInstanceTypes(clazz, "testEmptyClassWithInitializer"));
+ collectTypes(clazz, "testEmptyClassWithInitializer", "void"));
assertEquals(
Collections.singleton(
"com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal"),
- collectNewInstanceTypes(clazz, "testClassWithFinalizer"));
+ collectTypes(clazz, "testClassWithFinalizer", "void"));
assertEquals(
Collections.emptySet(),
- collectNewInstanceTypes(clazz, "testCallOnIface1"));
+ collectTypes(clazz, "testCallOnIface1", "void"));
assertEquals(
Collections.singleton(
"com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
- collectNewInstanceTypes(clazz, "testCallOnIface2"));
+ collectTypes(clazz, "testCallOnIface2", "void"));
assertEquals(
Sets.newHashSet(
"com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB",
"java.lang.StringBuilder"),
- collectNewInstanceTypes(clazz, "testCycles"));
+ collectTypes(clazz, "testCycles", "void"));
assertEquals(
Sets.newHashSet("java.lang.StringBuilder",
"com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
- collectNewInstanceTypes(inspector.clazz(CycleReferenceAB.class), "foo", "int"));
+ collectTypes(inspector.clazz(CycleReferenceAB.class), "foo", "void", "int"));
assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
}
@@ -139,12 +144,12 @@
ToolHelper.getClassAsBytes(PairBuilder.class),
ToolHelper.getClassAsBytes(ControlFlow.class),
};
- String main = BuildersTestClass.class.getCanonicalName();
- ProcessResult javaOutput = runOnJavaRaw(main, classes);
- assertEquals(0, javaOutput.exitCode);
-
AndroidApp app = runR8(buildAndroidApp(classes), BuildersTestClass.class);
+ String javaOutput = runOnJava(BuildersTestClass.class);
+ String artOutput = runOnArt(app, BuildersTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
DexInspector inspector = new DexInspector(app);
ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
@@ -152,7 +157,7 @@
Sets.newHashSet(
"com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
"java.lang.StringBuilder"),
- collectNewInstanceTypes(clazz, "testSimpleBuilder"));
+ collectTypes(clazz, "testSimpleBuilder", "void"));
// Note that Pair created instances were also inlined in the following method since
// we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
@@ -160,25 +165,25 @@
// would make it not eligible for inlining.
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
- collectNewInstanceTypes(clazz, "testSimpleBuilderWithMultipleBuilds"));
+ collectTypes(clazz, "testSimpleBuilderWithMultipleBuilds", "void"));
assertFalse(inspector.clazz(PairBuilder.class).isPresent());
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
- collectNewInstanceTypes(clazz, "testBuilderConstructors"));
+ collectTypes(clazz, "testBuilderConstructors", "void"));
assertFalse(inspector.clazz(Tuple.class).isPresent());
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
- collectNewInstanceTypes(clazz, "testWithControlFlow"));
+ collectTypes(clazz, "testWithControlFlow", "void"));
assertFalse(inspector.clazz(ControlFlow.class).isPresent());
assertEquals(
Collections.emptySet(),
- collectNewInstanceTypes(clazz, "testWithMoreControlFlow"));
+ collectTypes(clazz, "testWithMoreControlFlow", "void"));
assertFalse(inspector.clazz(BuildersTestClass.Pos.class).isPresent());
}
@@ -216,14 +221,65 @@
assertThat(artResult.stderr, containsString("IncompatibleClassChangeError"));
}
- private Set<String> collectNewInstanceTypes(
- ClassSubject clazz, String methodName, String... params) {
+ @Test
+ public void testCodeSample() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(C.class),
+ ToolHelper.getClassAsBytes(C.L.class),
+ ToolHelper.getClassAsBytes(C.F.class),
+ ToolHelper.getClassAsBytes(CodeTestClass.class)
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), CodeTestClass.class);
+
+ String javaOutput = runOnJava(CodeTestClass.class);
+ String artOutput = runOnArt(app, CodeTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(C.class);
+
+ assertEquals(
+ Collections.emptySet(),
+ collectTypes(clazz, "method1", "int"));
+
+ assertEquals(
+ Collections.emptySet(),
+ collectTypes(clazz, "method2", "int"));
+
+ assertEquals(
+ Collections.emptySet(),
+ collectTypes(clazz, "method3", "int"));
+
+ assertFalse(inspector.clazz(C.L.class).isPresent());
+ assertFalse(inspector.clazz(C.F.class).isPresent());
+ }
+
+ private Set<String> collectTypes(
+ ClassSubject clazz, String methodName, String retValue, String... params) {
+ return Stream.concat(
+ collectNewInstanceTypesWithRetValue(clazz, methodName, retValue, params),
+ collectStaticGetTypesWithRetValue(clazz, methodName, retValue, params)
+ ).collect(Collectors.toSet());
+ }
+
+ private Stream<String> collectNewInstanceTypesWithRetValue(
+ ClassSubject clazz, String methodName, String retValue, String... params) {
assertNotNull(clazz);
- MethodSignature signature = new MethodSignature(methodName, "void", params);
+ MethodSignature signature = new MethodSignature(methodName, retValue, params);
DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
return filterInstructionKind(code, NewInstance.class)
- .map(insn -> ((NewInstance) insn).getType().toSourceString())
- .collect(Collectors.toSet());
+ .map(insn -> ((NewInstance) insn).getType().toSourceString());
+ }
+
+ private Stream<String> collectStaticGetTypesWithRetValue(
+ ClassSubject clazz, String methodName, String retValue, String... params) {
+ assertNotNull(clazz);
+ MethodSignature signature = new MethodSignature(methodName, retValue, params);
+ DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+ return filterInstructionKind(code, Sget.class)
+ .map(Instruction::getField)
+ .filter(field -> field.clazz == field.type)
+ .map(field -> field.clazz.toSourceString());
}
private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
new file mode 100644
index 0000000..38e75c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
@@ -0,0 +1,39 @@
+// 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.code;
+
+public class C {
+ public static class L {
+ public final int x;
+
+ public L(int x) {
+ this.x = x;
+ }
+
+ public int getX() {
+ return x;
+ }
+ }
+
+ public final static class F {
+ public final static F I = new F();
+
+ public int getX() {
+ return 123;
+ }
+ }
+
+ public synchronized static int method1() {
+ return new L(1).x;
+ }
+
+ public synchronized static int method2() {
+ return new L(1).getX();
+ }
+
+ public synchronized static int method3() {
+ return F.I.getX();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/CodeTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/CodeTestClass.java
new file mode 100644
index 0000000..eb5dc10
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/CodeTestClass.java
@@ -0,0 +1,14 @@
+// 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.code;
+
+public class CodeTestClass {
+ public static void main(String[] args) {
+ System.out.println(C.method1());
+ System.out.println(C.method2());
+ System.out.println(C.method3());
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
new file mode 100644
index 0000000..eaae740
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -0,0 +1,108 @@
+// 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.kotlin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+
+public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
+ private static boolean isLambda(DexClass clazz) {
+ return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
+ (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz));
+ }
+
+ private static boolean isKStyleLambdaOrGroup(DexClass clazz) {
+ return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
+ }
+
+ private static boolean isJStyleLambdaOrGroup(DexClass clazz) {
+ return clazz.superType.descriptor.toString().equals("Ljava/lang/Object;") &&
+ clazz.interfaces.size() == 1;
+ }
+
+ private static Predicate<DexType> createLambdaCheck(DexInspector inspector) {
+ Set<DexType> lambdaClasses = inspector.allClasses().stream()
+ .filter(clazz -> isLambda(clazz.getDexClass()))
+ .map(clazz -> clazz.getDexClass().type)
+ .collect(Collectors.toSet());
+ return lambdaClasses::contains;
+ }
+
+ @Test
+ public void testJStyleLambdas() throws Exception {
+ final String mainClassName = "class_inliner_lambda_j_style.MainKt";
+ runTest("class_inliner_lambda_j_style", mainClassName, (app) -> {
+ DexInspector inspector = new DexInspector(app);
+ Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
+ ClassSubject clazz = inspector.clazz(mainClassName);
+
+ assertEquals(
+ Sets.newHashSet(),
+ collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateless"));
+
+ assertEquals(
+ Sets.newHashSet(
+ "class_inliner_lambda_j_style.MainKt$testStateful$3"),
+ collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful"));
+
+ assertFalse(
+ inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
+
+ assertEquals(
+ Sets.newHashSet(
+ "class_inliner_lambda_j_style.MainKt$testStateful2$1"),
+ collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful2"));
+
+ assertEquals(
+ Sets.newHashSet(
+ "class_inliner_lambda_j_style.MainKt$testStateful3$1"),
+ collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful3"));
+ });
+ }
+
+ private Set<String> collectAccessedLambdaTypes(
+ Predicate<DexType> isLambdaType, ClassSubject clazz, String methodName, String... params) {
+ assertNotNull(clazz);
+ MethodSignature signature = new MethodSignature(methodName, "void", params);
+ DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+ return Stream.concat(
+ filterInstructionKind(code, NewInstance.class)
+ .map(insn -> ((NewInstance) insn).getType()),
+ filterInstructionKind(code, SgetObject.class)
+ .map(insn -> insn.getField().getHolder())
+ )
+ .filter(isLambdaType)
+ .map(DexType::toSourceString)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ protected void runTest(String folder, String mainClass,
+ AndroidAppInspector inspector) throws Exception {
+ runTest(
+ folder, mainClass, null,
+ options -> {
+ options.enableInlining = true;
+ options.enableClassInlining = true;
+ options.enableLambdaMerging = false;
+ }, inspector);
+ }
+}
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 6ebb08b..dc9b642 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -587,7 +587,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "primitiveProp";
@@ -614,7 +614,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "privateProp";
@@ -641,7 +641,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "internalProp";
@@ -668,7 +668,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "publicProp";
@@ -695,7 +695,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitPrivateProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
@@ -722,7 +722,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitInternalProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
@@ -745,7 +745,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitPublicProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
@@ -768,7 +768,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "primitiveProp";
@@ -795,7 +795,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "privateProp";
@@ -821,7 +821,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "internalProp";
@@ -847,7 +847,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "publicProp";
@@ -874,7 +874,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitPrivateProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject fileClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
@@ -900,7 +900,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitInternalProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
@@ -924,7 +924,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitPublicProp");
- runTest(PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index 9edb509..61b1454 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -155,7 +155,8 @@
runTest(
mainClass,
ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
- keepMainProguardConfiguration(mainClass),
+ // Prevent SuperClass from being merged into SubClass.
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
this::checkAllClassesPresentWithDefaultConstructor);
}
@@ -166,7 +167,8 @@
runTest(
mainClass,
ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
- keepMainProguardConfiguration(mainClass),
+ // Prevent SuperClass from being merged into SubClass.
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
this::checkAllClassesPresentWithDefaultConstructor);
}
@@ -177,7 +179,8 @@
runTest(
mainClass,
ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
- keepMainProguardConfiguration(mainClass),
+ // Prevent SuperClass from being merged into SubClass.
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
// TODO(74423424): Proguard eliminates the check-cast on null.
this::checkAllClassesPresentWithDefaultConstructor,
this::checkOnlyMainPresent);
@@ -191,7 +194,9 @@
runTest(
mainClass,
ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
- keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ // Prevent SuperClass from being merged into SubClass.
+ keepMainProguardConfiguration(
+ mainClass, ImmutableList.of("-dontoptimize", "-keep class **.SuperClass")),
this::checkAllClassesPresentWithDefaultConstructor);
}
@@ -202,7 +207,8 @@
runTest(
mainClass,
ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
- keepMainProguardConfiguration(mainClass),
+ // Prevent SuperClass from being merged into SubClass.
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
this::checkAllClassesPresentWithDefaultConstructor);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 73c3011..7480e2e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -187,19 +187,20 @@
@Test
public void ifOnFieldInImplementer_withoutNthWildcard() throws Exception {
- List<String> config = ImmutableList.of(
- "-keep class **.MainUsesImpl {",
- " public static void main(java.lang.String[]);",
- "}",
- "-if class ** implements **.I {",
- " private <fields>;",
- "}",
- "-keep class **.D1",
- "-if class ** implements **.I {",
- " public <fields>;",
- "}",
- "-keep class **.D2"
- );
+ List<String> config =
+ ImmutableList.of(
+ "-keep class **.MainUsesImpl {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-keep class **.I", // Prevent I from being merged into Impl.
+ "-if class ** implements **.I {",
+ " private <fields>;",
+ "}",
+ "-keep class **.D1",
+ "-if class ** implements **.I {",
+ " public <fields>;",
+ "}",
+ "-keep class **.D2");
DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
verifyClassesAbsent(dexInspector, D2.class);
@@ -209,19 +210,20 @@
@Test
public void ifOnFieldInImplementer_withNthWildcard() throws Exception {
- List<String> config = ImmutableList.of(
- "-keep class **.MainUsesImpl {",
- " public static void main(java.lang.String[]);",
- "}",
- "-if class ** implements **.I {",
- " private <fields>;",
- "}",
- "-keep class <2>.D1",
- "-if class ** implements **.I {",
- " public <fields>;",
- "}",
- "-keep class <2>.D2"
- );
+ List<String> config =
+ ImmutableList.of(
+ "-keep class **.MainUsesImpl {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-keep class **.I", // Prevent I from being merged into Impl.
+ "-if class ** implements **.I {",
+ " private <fields>;",
+ "}",
+ "-keep class <2>.D1",
+ "-if class ** implements **.I {",
+ " public <fields>;",
+ "}",
+ "-keep class <2>.D2");
DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
verifyClassesAbsent(dexInspector, D2.class);
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/SamIface.java b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/SamIface.java
new file mode 100644
index 0000000..bccef3e
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/SamIface.java
@@ -0,0 +1,21 @@
+// 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 class_inliner_lambda_j_style;
+
+public interface SamIface {
+ String foo();
+
+ class Consumer {
+ public static void consume(SamIface iface) {
+ System.out.println(iface.foo());
+ }
+
+ public static void consumeBig(SamIface iface) {
+ System.out.println("Bigger than inline limit, class name: " + iface.getClass().getName());
+ System.out.println("Bigger than inline limit, result: '" + iface.foo() + "'");
+ consume(iface);
+ }
+ }
+}
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
new file mode 100644
index 0000000..ebf26cd
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
@@ -0,0 +1,99 @@
+// 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 class_inliner_lambda_j_style
+
+private var COUNT = 0
+
+fun nextInt() = COUNT++
+fun next() = "${nextInt()}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+ testStateless()
+ testStateful()
+ testStateful2()
+ testStateful3()
+}
+
+@Synchronized
+fun testStateless() {
+ SamIface.Consumer.consume { "123" }
+ SamIface.Consumer.consume { "ABC" }
+ SamIface.Consumer.consume {
+ var x = 0
+ println("A: ${x++}")
+ println("B: ${x++}")
+ println("C: ${x++}")
+ println("D: ${x++}")
+ println("E: ${x++}")
+ "outer + $x"
+ }
+}
+
+@Synchronized
+fun testStateful() {
+ var someVariable = 0
+
+ SamIface.Consumer.consume {
+ println("A: someVariable = $someVariable")
+ someVariable += 1
+ "B: someVariable = $someVariable"
+ }
+ SamIface.Consumer.consume {
+ SamIface.Consumer.consume {
+ println("E: someVariable = $someVariable")
+ someVariable += 1
+ "F: someVariable = $someVariable"
+ }
+ for (i in 1..20) {
+ someVariable += 1
+ if (i % 4 == 0) {
+ println("G: someVariable = $someVariable")
+ }
+ }
+ someVariable += 1
+ "H: someVariable = $someVariable"
+ }
+ SamIface.Consumer.consumeBig {
+ println("I: someVariable = $someVariable")
+ someVariable += 1
+ "J: someVariable = $someVariable"
+ }
+}
+
+@Synchronized
+fun testStateful2() {
+ var someVariable = 0
+ SamIface.Consumer.consumeBig {
+ println("[Z] A: someVariable = $someVariable")
+ someVariable += 1
+ println("[Z] B: someVariable = $someVariable")
+ someVariable += 1
+ println("[Z] C: someVariable = $someVariable")
+ someVariable += 1
+ println("[Z] D: someVariable = $someVariable")
+ someVariable += 1
+ println("[Z] E: someVariable = $someVariable")
+ someVariable += 1
+ "[Z] F: someVariable = $someVariable"
+ }
+}
+
+@Synchronized
+fun testStateful3() {
+ var someVariable = 0
+ SamIface.Consumer.consumeBig {
+ println("[W] A: someVariable = $someVariable")
+ someVariable += 1
+ println("[W] B: someVariable = $someVariable")
+ someVariable += 1
+ println("[W] C: someVariable = $someVariable")
+ someVariable += 1
+ println("[W] D: someVariable = $someVariable")
+ someVariable += 1
+ println("[W] E: someVariable = $someVariable")
+ someVariable += 1
+ "[W] F: someVariable = $someVariable"
+ }
+}