Preliminary support for outlining non-startup from startup
Bug: b/275292237
Change-Id: I0674ec04b806082512997ebeb57c8dd7a757cad8
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 851df61..ce2e296 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -105,6 +105,7 @@
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.startup.NonStartupInStartupOutliner;
import com.android.tools.r8.synthesis.SyntheticFinalization;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.AndroidApp;
@@ -706,6 +707,8 @@
appView.getArtProfileCollection().withoutMissingItems(appView));
appView.setStartupProfile(appView.getStartupProfile().withoutMissingItems(appView));
+ new NonStartupInStartupOutliner(appView).runIfNecessary(executorService, timing);
+
if (appView.appInfo().hasLiveness()) {
SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService, timing);
} else {
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 825655a..175abb4 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -269,8 +269,9 @@
return newAccessFlags;
}
- public void promoteToStatic() {
+ public T promoteToStatic() {
promote(Constants.ACC_STATIC);
+ return self();
}
private boolean wasSet(int flag) {
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 d6216c3..30f63fd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1003,10 +1003,12 @@
DexDebugInfo newDebugInfo = dexCode.debugInfoWithFakeThisParameter(appView.dexItemFactory());
assert (newDebugInfo == null) || (arity == newDebugInfo.getParameterCount());
dexCode.setDebugInfo(newDebugInfo);
- } else {
- assert code.isCfCode();
+ } else if (code.isCfCode()) {
CfCode cfCode = code.asCfCode();
cfCode.addFakeThisParameter(appView.dexItemFactory());
+ } else if (code.isLirCode()) {
+ assert appView.options().isRelease();
+ assert code.asLirCode().getDebugLocalInfoTable() == null;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index 2aad1c9..e198ebf 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -460,6 +460,10 @@
return null;
}
+ public boolean isNonStartupInStartupOutlinerLens() {
+ return false;
+ }
+
public boolean isProtoNormalizerLens() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
index 0dd4db5..7cd5d83 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
@@ -69,6 +69,7 @@
private static class Traversal extends DepthFirstTopDownClassHierarchyTraversal {
+ private final AppView<AppInfoWithLiveness> appViewWithLiveness;
private final MethodResolutionOptimizationInfoCollection.Builder builder;
private final Map<DexProgramClass, TraversalState> states = new IdentityHashMap<>();
@@ -77,6 +78,7 @@
MethodResolutionOptimizationInfoCollection.Builder builder,
ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
super(appView, immediateSubtypingInfo);
+ this.appViewWithLiveness = appView;
this.builder = builder;
}
@@ -145,7 +147,7 @@
states
.getOrDefault(subClass, UpwardsTraversalState.empty())
.asUpwardsTraversalState();
- newState.join(appView, subClassState);
+ newState.join(appViewWithLiveness, subClassState);
// If the current class is an interface and the current subclass is not, then we need
// special handling to account for the fact that invoke-interface instructions may
@@ -156,26 +158,30 @@
}
});
ObjectAllocationInfoCollection objectAllocationInfoCollection =
- appView.appInfo().getObjectAllocationInfoCollection();
+ appViewWithLiveness.appInfo().getObjectAllocationInfoCollection();
if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(clazz)) {
for (DexEncodedMethod method : clazz.virtualMethods()) {
newState.joinMethodOptimizationInfo(
- appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance());
+ appViewWithLiveness,
+ method.getSignature(),
+ DefaultMethodOptimizationInfo.getInstance());
}
} else {
for (DexEncodedMethod method : clazz.virtualMethods()) {
- KeepMethodInfo keepInfo = appView.getKeepInfo().getMethodInfo(method, clazz);
- if (!keepInfo.isShrinkingAllowed(appView.options())) {
+ KeepMethodInfo keepInfo = appViewWithLiveness.getKeepInfo().getMethodInfo(method, clazz);
+ if (!keepInfo.isShrinkingAllowed(appViewWithLiveness.options())) {
// Method is kept and could be overridden outside app (e.g., in tests). Verify we don't
// have any optimization info recorded for non-abstract methods.
assert method.isAbstract()
|| method.getOptimizationInfo().isDefault()
|| method.getOptimizationInfo().returnValueHasBeenPropagated();
newState.joinMethodOptimizationInfo(
- appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance());
+ appViewWithLiveness,
+ method.getSignature(),
+ DefaultMethodOptimizationInfo.getInstance());
} else if (!method.isAbstract()) {
newState.joinMethodOptimizationInfo(
- appView, method.getSignature(), method.getOptimizationInfo());
+ appViewWithLiveness, method.getSignature(), method.getOptimizationInfo());
}
}
}
@@ -214,7 +220,7 @@
for (DexMethodSignature method : interfaceMethodsInClassOrAbove) {
MethodResolutionResult resolutionResult =
- appView.appInfo().resolveMethodOnClass(subClass, method);
+ appViewWithLiveness.appInfo().resolveMethodOnClass(subClass, method);
if (resolutionResult.isFailedResolution()) {
assert resolutionResult.asFailedResolution().hasMethodsCausingError();
continue;
@@ -223,7 +229,7 @@
if (resolutionResult.isMultiMethodResolutionResult()) {
// Conservatively drop the current optimization info.
newState.joinMethodOptimizationInfo(
- appView, method, DefaultMethodOptimizationInfo.getInstance());
+ appViewWithLiveness, method, DefaultMethodOptimizationInfo.getInstance());
continue;
}
@@ -231,7 +237,7 @@
DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
if (!resolvedMethod.getHolder().isInterface() && resolvedMethod.getHolder() != subClass) {
newState.joinMethodOptimizationInfo(
- appView, method, resolvedMethod.getOptimizationInfo());
+ appViewWithLiveness, method, resolvedMethod.getOptimizationInfo());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
index b5ab4d1..d14a8bd 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.graph.lens.FieldLookupResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens;
import com.android.tools.r8.ir.code.IRCode;
@@ -54,6 +55,8 @@
private final GraphLens codeLens;
private final LensCodeRewriterUtils helper;
+ private final boolean isNonStartupInStartupOutlinerLens;
+
private int numberOfInvokeOpcodeChanges = 0;
private Map<LirConstant, LirConstant> constantPoolMapping = null;
@@ -71,6 +74,15 @@
this.graphLens = appView.graphLens();
this.codeLens = context.getDefinition().getCode().getCodeLens(appView);
this.helper = helper;
+ NonIdentityGraphLens nonStartupInStartupOutlinerLens =
+ graphLens.isNonIdentityLens()
+ ? graphLens
+ .asNonIdentityLens()
+ .find(l -> l.isNonStartupInStartupOutlinerLens() || l == codeLens)
+ : null;
+ this.isNonStartupInStartupOutlinerLens =
+ nonStartupInStartupOutlinerLens != null
+ && nonStartupInStartupOutlinerLens.isNonStartupInStartupOutlinerLens();
}
@Override
@@ -120,13 +132,14 @@
InvokeType newType = result.getType();
boolean newIsInterface = lookupIsInterface(method, opcode, result);
int newOpcode = newType.getLirOpcode(newIsInterface);
- assert newMethod.getArity() == method.getArity();
+ assert newMethod.getArity() == method.getArity() || newType.isStatic();
if (newOpcode != opcode) {
assert type == newType
- || (type.isDirect() && (newType.isInterface() || newType.isVirtual()))
- || (type.isInterface() && newType.isVirtual())
+ || (type.isDirect()
+ && (newType.isInterface() || newType.isStatic() || newType.isVirtual()))
+ || (type.isInterface() && (newType.isStatic() || newType.isVirtual()))
|| (type.isSuper() && newType.isVirtual())
- || (type.isVirtual() && newType.isInterface())
+ || (type.isVirtual() && (newType.isInterface() || newType.isStatic()))
: type + " -> " + newType;
numberOfInvokeOpcodeChanges++;
} else {
@@ -281,6 +294,17 @@
if (opcode == LirOpcodes.INVOKEINTERFACE) {
return InvokeType.INTERFACE;
}
+ if (isNonStartupInStartupOutlinerLens) {
+ if (LirOpcodeUtils.isInvokeDirect(opcode)) {
+ return InvokeType.DIRECT;
+ }
+ if (LirOpcodeUtils.isInvokeInterface(opcode)) {
+ return InvokeType.INTERFACE;
+ }
+ if (LirOpcodeUtils.isInvokeVirtual(opcode)) {
+ return InvokeType.VIRTUAL;
+ }
+ }
if (graphLens.isVerticalClassMergerLens()) {
if (opcode == LirOpcodes.INVOKESTATIC_ITF) {
return InvokeType.STATIC;
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
index 7b110eb..ec788d1 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
@@ -54,6 +54,14 @@
}
}
+ public static boolean isInvokeDirect(int opcode) {
+ return opcode == INVOKEDIRECT || opcode == INVOKEDIRECT_ITF;
+ }
+
+ public static boolean isInvokeInterface(int opcode) {
+ return opcode == INVOKEINTERFACE;
+ }
+
public static boolean isInvokeMethod(int opcode) {
switch (opcode) {
case INVOKEDIRECT:
@@ -69,4 +77,8 @@
return false;
}
}
+
+ public static boolean isInvokeVirtual(int opcode) {
+ return opcode == INVOKEVIRTUAL;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
index c380eab..28d6b5c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -10,7 +11,6 @@
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
@@ -140,7 +140,8 @@
protected final Map<DexMethod, DexMethod> virtualRootMethods = new IdentityHashMap<>();
protected VirtualRootMethodsAnalysisBase(
- AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
super(appView, immediateSubtypingInfo);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index 22b1a0e..c882a7c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -43,6 +43,7 @@
// Contains the argument information for each interface method (including inherited interface
// methods) on the seen but not finished interfaces.
+ final AppView<AppInfoWithLiveness> appViewWithLiveness;
final Map<DexProgramClass, MethodStateCollectionBySignature> methodStatesToPropagate =
new IdentityHashMap<>();
final Consumer<DexMethodSignature> interfaceDispatchOutsideProgram;
@@ -53,6 +54,7 @@
MethodStateCollectionByReference methodStates,
Consumer<DexMethodSignature> interfaceDispatchOutsideProgram) {
super(appView, immediateSubtypingInfo, methodStates);
+ this.appViewWithLiveness = appView;
this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
}
@@ -98,7 +100,7 @@
MethodStateCollectionBySignature implementedInterfaceState =
methodStatesToPropagate.get(superclass);
assert implementedInterfaceState != null;
- interfaceState.addMethodStates(appView, implementedInterfaceState);
+ interfaceState.addMethodStates(appViewWithLiveness, implementedInterfaceState);
});
// Add any argument information for virtual methods on the current interface to the state.
@@ -116,7 +118,7 @@
}
assert methodState.isUnknown() || methodState.asConcrete().isPolymorphic();
- interfaceState.addMethodState(appView, method, methodState);
+ interfaceState.addMethodState(appViewWithLiveness, method, methodState);
});
methodStatesToPropagate.put(interfaceDefinition, interfaceState);
@@ -134,7 +136,9 @@
interfaceState.forEach(
(interfaceMethod, interfaceMethodState) -> {
MethodResolutionResult resolutionResult =
- appView.appInfo().resolveMethodOnClassLegacy(subclass, interfaceMethod);
+ appViewWithLiveness
+ .appInfo()
+ .resolveMethodOnClassLegacy(subclass, interfaceMethod);
if (resolutionResult.isFailedResolution()) {
// TODO(b/190154391): Do we need to propagate argument information to the first
// virtual method above the inaccessible method in the class hierarchy?
@@ -155,10 +159,14 @@
MethodState transformedInterfaceMethodState =
transformInterfaceMethodStateForClassMethod(
- appView, subclass, resolvedMethod, interfaceMethodState, methodStates);
+ appViewWithLiveness,
+ subclass,
+ resolvedMethod,
+ interfaceMethodState,
+ methodStates);
if (!transformedInterfaceMethodState.isBottom()) {
methodStates.addMethodState(
- appView, resolvedMethod, transformedInterfaceMethodState);
+ appViewWithLiveness, resolvedMethod, transformedInterfaceMethodState);
}
}));
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index e14addb..14eb036 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -70,14 +70,14 @@
assert parentState != null;
// Add the argument information that must be propagated to all method overrides.
- active.addMethodStates(appView, parentState.active);
+ active.addMethodStates(appViewWithLiveness, parentState.active);
// Add the argument information that is active until a given lower bound.
parentState.activeUntilLowerBound.forEach(
(lowerBound, activeMethodState) -> {
- TypeElement lowerBoundType = lowerBound.toTypeElement(appView);
- TypeElement currentType = clazz.getType().toTypeElement(appView);
- if (lowerBoundType.lessThanOrEqual(currentType, appView)) {
+ TypeElement lowerBoundType = lowerBound.toTypeElement(appViewWithLiveness);
+ TypeElement currentType = clazz.getType().toTypeElement(appViewWithLiveness);
+ if (lowerBoundType.lessThanOrEqual(currentType, appViewWithLiveness)) {
addActiveUntilLowerBound(lowerBound, activeMethodState);
} else {
// No longer active.
@@ -109,21 +109,22 @@
// interface method is not applied. The information is propagated to the class
// method that implements the interface method below.
ClassTypeElement lowerBound = bounds.getDynamicLowerBoundType();
- TypeElement currentType = clazz.getType().toTypeElement(appView);
- if (lowerBound.lessThanOrEqual(currentType, appView)) {
- DexType activeUntilLowerBoundType = lowerBound.toDexType(appView.dexItemFactory());
+ TypeElement currentType = clazz.getType().toTypeElement(appViewWithLiveness);
+ if (lowerBound.lessThanOrEqual(currentType, appViewWithLiveness)) {
+ DexType activeUntilLowerBoundType =
+ lowerBound.toDexType(appViewWithLiveness.dexItemFactory());
addActiveUntilLowerBound(activeUntilLowerBoundType, inactiveMethodStates);
} else {
return;
}
} else {
- active.addMethodStates(appView, inactiveMethodStates);
+ active.addMethodStates(appViewWithLiveness, inactiveMethodStates);
}
inactiveMethodStates.forEach(
(signature, methodState) -> {
SingleResolutionResult<?> resolutionResult =
- appView
+ appViewWithLiveness
.appInfo()
.resolveMethodOnLegacy(clazz, signature)
.asSingleResolution();
@@ -132,7 +133,7 @@
while (resolutionResult != null
&& resolutionResult.getResolvedMethod().belongsToDirectPool()) {
resolutionResult =
- appView
+ appViewWithLiveness
.appInfo()
.resolveMethodOnClassLegacy(
resolutionResult.getResolvedHolder().getSuperType(), signature)
@@ -160,28 +161,28 @@
DexType lowerBound, ProgramMethod method, MethodState methodState) {
activeUntilLowerBound
.computeIfAbsent(lowerBound, ignoreKey(MethodStateCollectionBySignature::create))
- .addMethodState(appView, method, methodState);
+ .addMethodState(appViewWithLiveness, method, methodState);
}
private void addActiveUntilLowerBound(
DexType lowerBound, MethodStateCollectionBySignature methodStates) {
activeUntilLowerBound
.computeIfAbsent(lowerBound, ignoreKey(MethodStateCollectionBySignature::create))
- .addMethodStates(appView, methodStates);
+ .addMethodStates(appViewWithLiveness, methodStates);
}
private void addInactiveUntilUpperBound(
DynamicTypeWithUpperBound upperBound, ProgramMethod method, MethodState methodState) {
inactiveUntilUpperBound
.computeIfAbsent(upperBound, ignoreKey(MethodStateCollectionBySignature::create))
- .addMethodState(appView, method, methodState);
+ .addMethodState(appViewWithLiveness, method, methodState);
}
private void addInactiveUntilUpperBound(
DynamicTypeWithUpperBound upperBound, MethodStateCollectionBySignature methodStates) {
inactiveUntilUpperBound
.computeIfAbsent(upperBound, ignoreKey(MethodStateCollectionBySignature::create))
- .addMethodStates(appView, methodStates);
+ .addMethodStates(appViewWithLiveness, methodStates);
}
private MethodState computeMethodStateForPolymorphicMethod(ProgramMethod method) {
@@ -192,7 +193,10 @@
for (MethodStateCollectionBySignature methodStates : activeUntilLowerBound.values()) {
methodState =
methodState.mutableJoin(
- appView, methodSignature, methodStates.get(method), StateCloner.getCloner());
+ appViewWithLiveness,
+ methodSignature,
+ methodStates.get(method),
+ StateCloner.getCloner());
}
}
if (methodState.isMonomorphic()) {
@@ -226,8 +230,9 @@
dynamicType.asDynamicTypeWithUpperBound();
TypeElement dynamicUpperBoundType = dynamicTypeWithUpperBound.getDynamicUpperBoundType();
TypeElement staticUpperBoundType =
- method.getHolderType().toTypeElement(appView, definitelyNotNull());
- if (dynamicUpperBoundType.lessThanOrEqualUpToNullability(staticUpperBoundType, appView)) {
+ method.getHolderType().toTypeElement(appViewWithLiveness, definitelyNotNull());
+ if (dynamicUpperBoundType.lessThanOrEqualUpToNullability(
+ staticUpperBoundType, appViewWithLiveness)) {
DynamicType newDynamicType = dynamicType.withNullability(definitelyNotNull());
assert newDynamicType.equals(dynamicType)
|| !dynamicType.getNullability().isDefinitelyNotNull();
@@ -237,25 +242,27 @@
if (dynamicLowerBoundType == null) {
return DynamicType.definitelyNotNull();
}
- assert dynamicLowerBoundType.lessThanOrEqualUpToNullability(staticUpperBoundType, appView);
+ assert dynamicLowerBoundType.lessThanOrEqualUpToNullability(
+ staticUpperBoundType, appViewWithLiveness);
if (dynamicLowerBoundType.equalUpToNullability(staticUpperBoundType)) {
return DynamicType.createExact(dynamicLowerBoundType.asDefinitelyNotNull());
}
return DynamicType.create(
- appView, staticUpperBoundType, dynamicLowerBoundType.asDefinitelyNotNull());
+ appViewWithLiveness, staticUpperBoundType, dynamicLowerBoundType.asDefinitelyNotNull());
}
@SuppressWarnings("ReferenceEquality")
private boolean shouldActivateMethodStateGuardedByBounds(
ClassTypeElement upperBound, DexProgramClass currentClass, DexProgramClass superClass) {
ClassTypeElement classType =
- TypeElement.fromDexType(currentClass.getType(), maybeNull(), appView).asClassType();
+ TypeElement.fromDexType(currentClass.getType(), maybeNull(), appViewWithLiveness)
+ .asClassType();
// When propagating argument information for interface methods downwards from an interface to
// a non-interface we need to account for the parent classes of the current class.
if (superClass.isInterface()
&& !currentClass.isInterface()
- && currentClass.getSuperType() != appView.dexItemFactory().objectType) {
- return classType.lessThanOrEqualUpToNullability(upperBound, appView);
+ && currentClass.getSuperType() != appViewWithLiveness.dexItemFactory().objectType) {
+ return classType.lessThanOrEqualUpToNullability(upperBound, appViewWithLiveness);
}
// If the upper bound does not have any interfaces we simply activate the method state when
// meeting the upper bound class type in the downwards traversal over the class hierarchy.
@@ -264,19 +271,21 @@
}
// If the upper bound has interfaces, we check if the current class is a subtype of *both* the
// upper bound class type and the upper bound interface types.
- return classType.lessThanOrEqualUpToNullability(upperBound, appView);
+ return classType.lessThanOrEqualUpToNullability(upperBound, appViewWithLiveness);
}
boolean verifyActiveUntilLowerBoundRelevance(DexProgramClass clazz) {
- TypeElement currentType = clazz.getType().toTypeElement(appView);
+ TypeElement currentType = clazz.getType().toTypeElement(appViewWithLiveness);
for (DexType lowerBound : activeUntilLowerBound.keySet()) {
- TypeElement lowerBoundType = lowerBound.toTypeElement(appView);
- assert lowerBoundType.lessThanOrEqual(currentType, appView);
+ TypeElement lowerBoundType = lowerBound.toTypeElement(appViewWithLiveness);
+ assert lowerBoundType.lessThanOrEqual(currentType, appViewWithLiveness);
}
return true;
}
}
+ final AppView<AppInfoWithLiveness> appViewWithLiveness;
+
// For each class, stores the argument information for each virtual method on this class and all
// direct and indirect super classes.
//
@@ -290,6 +299,7 @@
ImmediateProgramSubtypingInfo immediateSubtypingInfo,
MethodStateCollectionByReference methodStates) {
super(appView, immediateSubtypingInfo, methodStates);
+ this.appViewWithLiveness = appView;
}
@Override
@@ -336,7 +346,8 @@
polymorphicMethodState.forEach(
(bounds, methodStateForBounds) -> {
if (bounds.isUnknown()) {
- propagationState.active.addMethodState(appView, method, methodStateForBounds);
+ propagationState.active.addMethodState(
+ appViewWithLiveness, method, methodStateForBounds);
} else {
// TODO(b/190154391): Verify that the bounds are not trivial according to the
// static receiver type.
@@ -347,19 +358,20 @@
// class.
ClassTypeElement lowerBound = bounds.getDynamicLowerBoundType();
DexType activeUntilLowerBoundType =
- lowerBound.toDexType(appView.dexItemFactory());
+ lowerBound.toDexType(appViewWithLiveness.dexItemFactory());
assert !bounds.isExactClassType()
|| activeUntilLowerBoundType.isIdenticalTo(clazz.getType());
propagationState.addActiveUntilLowerBound(
activeUntilLowerBoundType, method, methodStateForBounds);
} else {
- propagationState.active.addMethodState(appView, method, methodStateForBounds);
+ propagationState.active.addMethodState(
+ appViewWithLiveness, method, methodStateForBounds);
}
} else {
assert !clazz
.getType()
- .toTypeElement(appView)
- .lessThanOrEqualUpToNullability(upperBound, appView);
+ .toTypeElement(appViewWithLiveness)
+ .lessThanOrEqualUpToNullability(upperBound, appViewWithLiveness);
propagationState.addInactiveUntilUpperBound(
bounds, method, methodStateForBounds);
}
@@ -372,8 +384,9 @@
}
private boolean isUpperBoundSatisfied(ClassTypeElement upperBound, DexProgramClass currentClass) {
- DexType upperBoundType = upperBound.toDexType(appView.dexItemFactory());
- DexProgramClass upperBoundClass = asProgramClassOrNull(appView.definitionFor(upperBoundType));
+ DexType upperBoundType = upperBound.toDexType(appViewWithLiveness.dexItemFactory());
+ DexProgramClass upperBoundClass =
+ asProgramClassOrNull(appViewWithLiveness.definitionFor(upperBoundType));
if (upperBoundClass == null) {
// We should generally never have a dynamic receiver upper bound for a program method which is
// not a program class. However, since the program may not type change or there could be
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
index a843a58..3585041 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
@@ -6,11 +6,11 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -34,7 +34,7 @@
FINISHED
}
- protected final AppView<AppInfoWithLiveness> appView;
+ protected final AppView<? extends AppInfoWithClassHierarchy> appView;
protected final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
// Contains the traversal state for each class. If a given class is not in the map the class is
@@ -53,7 +53,8 @@
private final List<DexProgramClass> newlySeenButNotFinishedRoots = new ArrayList<>();
public DepthFirstTopDownClassHierarchyTraversal(
- AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
this.appView = appView;
this.immediateSubtypingInfo = immediateSubtypingInfo;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
index 00588b5..fbbf49f 100644
--- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexClass;
@@ -387,7 +388,7 @@
if (superTargets != null) {
return superTargets;
}
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView;
+ AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy = appView;
superTargets = ProgramMethodSet.create();
WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(root);
while (worklist.hasNext()) {
@@ -402,9 +403,10 @@
public void registerInvokeSuper(DexMethod method) {
ProgramMethod superTarget =
asProgramMethodOrNull(
- appViewWithLiveness
+ appViewWithClassHierarchy
.appInfo()
- .lookupSuperTarget(method, getContext(), appViewWithLiveness));
+ .lookupSuperTarget(
+ method, getContext(), appViewWithClassHierarchy));
if (superTarget != null) {
superTargets.add(superTarget);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java
index 8d589a8..77d3764 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java
@@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.singlecaller;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysisBase;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.List;
@@ -18,12 +18,13 @@
public class MonomorphicVirtualMethodsAnalysis extends VirtualRootMethodsAnalysisBase {
public MonomorphicVirtualMethodsAnalysis(
- AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
super(appView, immediateSubtypingInfo);
}
public static ProgramMethodSet computeMonomorphicVirtualRootMethods(
- AppView<AppInfoWithLiveness> appView,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
ImmediateProgramSubtypingInfo immediateSubtypingInfo,
List<Set<DexProgramClass>> stronglyConnectedComponents,
ExecutorService executorService)
@@ -43,7 +44,7 @@
}
private static ProgramMethodSet computeMonomorphicVirtualRootMethodsInComponent(
- AppView<AppInfoWithLiveness> appView,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
ImmediateProgramSubtypingInfo immediateSubtypingInfo,
Set<DexProgramClass> stronglyConnectedComponent) {
MonomorphicVirtualMethodsAnalysis analysis =
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
index 5645247..e16c128 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -69,7 +70,7 @@
public void run(ExecutorService executorService) throws ExecutionException {
ProgramMethodSet monomorphicVirtualMethods =
- computeMonomorphicVirtualRootMethods(executorService);
+ computeMonomorphicVirtualRootMethods(appView, executorService);
ProgramMethodMap<ProgramMethod> singleCallerMethods =
new SingleCallerScanner(appView, monomorphicVirtualMethods)
.getSingleCallerMethods(executorService);
@@ -87,8 +88,8 @@
// deal with (rooted) virtual methods that do not override abstract/interface methods. In order to
// also deal with virtual methods that override abstract/interface methods we would need to record
// calls to the abstract/interface methods as calls to the non-abstract virtual method.
- @SuppressWarnings("UnusedMethod")
- private ProgramMethodSet computeMonomorphicVirtualRootMethods(ExecutorService executorService)
+ public static ProgramMethodSet computeMonomorphicVirtualRootMethods(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService)
throws ExecutionException {
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
diff --git a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
index e6406bc..62ee585 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
@@ -17,6 +17,14 @@
public class StartupOptions {
/**
+ * When enabled, attempts to move or outline all non-startup methods on startup classes.
+ *
+ * <p>Currently only supported in R8.
+ */
+ private boolean enableOutlining =
+ parseSystemPropertyOrDefault("com.android.tools.r8.startup.outline", false);
+
+ /**
* When enabled, all startup classes will be placed in the primary classes.dex file. All other
* (non-startup) classes will be placed in classes2.dex, ..., classesN.dex.
*/
@@ -68,6 +76,10 @@
Collections::emptyList);
}
+ public boolean isOutliningEnabled() {
+ return enableOutlining;
+ }
+
public boolean isMinimalStartupDexEnabled() {
return enableMinimalStartupDex;
}
@@ -99,6 +111,11 @@
return enableStartupLayoutOptimization;
}
+ public StartupOptions setEnableOutlining(boolean enableOutlining) {
+ this.enableOutlining = enableOutlining;
+ return this;
+ }
+
public StartupOptions setEnableStartupCompletenessCheckForTesting() {
return setEnableStartupCompletenessCheckForTesting(true);
}
diff --git a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
new file mode 100644
index 0000000..627c46b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
@@ -0,0 +1,382 @@
+// Copyright (c) 2024, 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.startup;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
+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.DexProgramClass;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.optimize.singlecaller.SingleCallerInliner;
+import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
+import com.android.tools.r8.profile.startup.profile.StartupProfile;
+import com.android.tools.r8.shaking.KeepMethodInfo.Joiner;
+import com.android.tools.r8.synthesis.CommittedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+
+public class NonStartupInStartupOutliner {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final DexItemFactory factory;
+ private final NonStartupInStartupOutlinerLens.Builder lensBuilder =
+ NonStartupInStartupOutlinerLens.builder();
+ private final StartupProfile startupProfile;
+ private final ProgramMethodSet syntheticMethods;
+
+ public NonStartupInStartupOutliner(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ this.startupProfile = appView.getStartupProfile();
+ this.syntheticMethods = ProgramMethodSet.createConcurrent();
+ }
+
+ public void runIfNecessary(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ if (!startupProfile.isEmpty() && appView.options().getStartupOptions().isOutliningEnabled()) {
+ timing.time("NonStartupInStartupOutliner", () -> run(executorService, timing));
+ }
+ }
+
+ private void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ Map<DexProgramClass, List<ProgramMethod>> methodsToOutline =
+ getMethodsToOutline(executorService);
+ if (methodsToOutline.isEmpty()) {
+ return;
+ }
+ ProfileCollectionAdditions profileCollectionAdditions =
+ ProfileCollectionAdditions.create(appView);
+ performOutlining(methodsToOutline, executorService, profileCollectionAdditions);
+ profileCollectionAdditions.commit(appView);
+ commitPendingSyntheticClasses();
+ setSyntheticKeepInfo();
+ rewriteWithLens(executorService, timing);
+ }
+
+ private Map<DexProgramClass, List<ProgramMethod>> getMethodsToOutline(
+ ExecutorService executorService) throws ExecutionException {
+ Map<DexProgramClass, List<ProgramMethod>> methodsToOutline = new ConcurrentHashMap<>();
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz ->
+ forEachMethodToOutline(
+ clazz,
+ method ->
+ methodsToOutline.computeIfAbsent(clazz, ignoreKey(ArrayList::new)).add(method)),
+ appView.options().getThreadingModule(),
+ executorService);
+ return methodsToOutline;
+ }
+
+ private void forEachMethodToOutline(DexProgramClass clazz, Consumer<ProgramMethod> fn) {
+ if (!startupProfile.isStartupClass(clazz.getType())) {
+ return;
+ }
+ clazz.forEachProgramMethodMatching(
+ DexEncodedMethod::hasCode,
+ method -> {
+ if (!startupProfile.containsMethodRule(method.getReference())
+ && !method.getDefinition().isInitializer()
+ && !hasPrivateOrSuperAccess(method)) {
+ fn.accept(method);
+ }
+ });
+ }
+
+ // TODO(b/275292237): Extend to cover all possible accesses to private items (e.g., consider
+ // method handles).
+ private boolean hasPrivateOrSuperAccess(ProgramMethod method) {
+ return method.registerCodeReferencesWithResult(
+ new DefaultUseRegistryWithResult<>(appView, method, false) {
+
+ private AppInfoWithClassHierarchy appInfo() {
+ return NonStartupInStartupOutliner.this.appView.appInfo();
+ }
+
+ private void setHasPrivateAccess() {
+ setResult(true);
+ }
+
+ private void setHasSuperAccess() {
+ setResult(true);
+ }
+
+ // Field accesses.
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ registerFieldAccess(field);
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldAccess(field);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ registerFieldAccess(field);
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ registerFieldAccess(field);
+ }
+
+ private void registerFieldAccess(DexField field) {
+ DexClassAndField resolvedField =
+ appInfo().resolveField(field, getContext()).getResolutionPair();
+ if (resolvedField != null && resolvedField.getAccessFlags().isPrivate()) {
+ setHasPrivateAccess();
+ }
+ }
+
+ // Invokes.
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ registerInvokeMethod(appInfo().unsafeResolveMethodDueToDexFormat(method));
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ registerInvokeMethod(appInfo().resolveMethod(method, true));
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ registerInvokeMethod(appInfo().unsafeResolveMethodDueToDexFormat(method));
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ setHasSuperAccess();
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ registerInvokeMethod(appInfo().resolveMethod(method, false));
+ }
+
+ private void registerInvokeMethod(MethodResolutionResult resolutionResult) {
+ DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
+ if (resolvedMethod != null && resolvedMethod.getAccessFlags().isPrivate()) {
+ setHasPrivateAccess();
+ }
+ }
+ });
+ }
+
+ private void performOutlining(
+ Map<DexProgramClass, List<ProgramMethod>> methodsToOutline,
+ ExecutorService executorService,
+ ProfileCollectionAdditions profileCollectionAdditions)
+ throws ExecutionException {
+ // TODO(b/275292237): Only compute this information for virtual methods in startup classes.
+ ProcessorContext processorContext = appView.createProcessorContext();
+ ProgramMethodSet monomorphicVirtualMethods =
+ SingleCallerInliner.computeMonomorphicVirtualRootMethods(appView, executorService);
+ ThreadUtils.processMap(
+ methodsToOutline,
+ (clazz, methods) ->
+ performOutliningForClass(
+ clazz,
+ methods,
+ monomorphicVirtualMethods,
+ processorContext,
+ profileCollectionAdditions),
+ appView.options().getThreadingModule(),
+ executorService);
+ }
+
+ private void performOutliningForClass(
+ DexProgramClass clazz,
+ List<ProgramMethod> methodsToOutline,
+ ProgramMethodSet monomorphicVirtualMethods,
+ ProcessorContext processorContext,
+ ProfileCollectionAdditions profileCollectionAdditions) {
+ Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
+ for (ProgramMethod method : methodsToOutline) {
+ MethodProcessingContext methodProcessingContext =
+ processorContext.createMethodProcessingContext(method);
+ ProgramMethod syntheticMethod;
+ boolean isMove = isMoveable(method, monomorphicVirtualMethods);
+ if (isMove) {
+ syntheticMethod = performMove(method, methodProcessingContext);
+ methodsToRemove.add(method.getDefinition());
+ } else {
+ syntheticMethod = performOutliningForMethod(method, methodProcessingContext);
+ }
+ profileCollectionAdditions.applyIfContextIsInProfile(
+ method.getReference(),
+ additionsBuilder -> {
+ additionsBuilder
+ .addClassRule(syntheticMethod.getHolderType())
+ .addMethodRule(syntheticMethod.getReference());
+ if (isMove) {
+ additionsBuilder.removeMovedMethodRule(method, syntheticMethod);
+ }
+ });
+ syntheticMethods.add(syntheticMethod);
+ }
+ clazz.getMethodCollection().removeMethods(methodsToRemove);
+ }
+
+ private boolean isMoveable(ProgramMethod method, ProgramMethodSet monomorphicVirtualMethods) {
+ // If we extend this to D8 then we can never move any methods since this would require a mapping
+ // file for retracing.
+ assert appView.enableWholeProgramOptimizations();
+ if (!appView.getKeepInfo(method).isShrinkingAllowed(appView.options())) {
+ // Kept methods can never be moved.
+ return false;
+ }
+ if (method.getAccessFlags().isStatic()) {
+ // Static methods can always be moved. Class initialization side effects can be preserved by
+ // inserting an InitClass instruction in the beginning of the moved method.
+ return true;
+ }
+ if (method.getAccessFlags().isPrivate()) {
+ // Private methods have direct dispatch and can always be made public static.
+ return true;
+ }
+ // Virtual methods can only be staticized and moved if they are monomorphic.
+ assert method.getAccessFlags().belongsToVirtualPool();
+ return monomorphicVirtualMethods.contains(method);
+ }
+
+ private ProgramMethod performMove(
+ ProgramMethod method, MethodProcessingContext methodProcessingContext) {
+ ProgramMethod movedMethod =
+ createSyntheticMethod(
+ method,
+ methodProcessingContext,
+ method.getAccessFlags().copy().promoteToPublic().promoteToStatic());
+
+ // Record the move in the lens for correct lens code rewriting.
+ lensBuilder.recordMove(method, movedMethod);
+
+ return movedMethod;
+ }
+
+ private ProgramMethod performOutliningForMethod(
+ ProgramMethod method, MethodProcessingContext methodProcessingContext) {
+ ProgramMethod outlinedMethod =
+ createSyntheticMethod(
+ method, methodProcessingContext, MethodAccessFlags.createPublicStaticSynthetic());
+
+ // Rewrite the non-synthetic method to call the synthetic method.
+ method.setCode(
+ ForwardMethodBuilder.builder(factory)
+ .applyIf(
+ method.getAccessFlags().isStatic(),
+ codeBuilder -> codeBuilder.setStaticSource(method.getReference()),
+ codeBuilder -> codeBuilder.setNonStaticSource(method.getReference()))
+ .setStaticTarget(outlinedMethod.getReference(), false)
+ .buildLir(appView),
+ appView);
+
+ return outlinedMethod;
+ }
+
+ private ProgramMethod createSyntheticMethod(
+ ProgramMethod method,
+ MethodProcessingContext methodProcessingContext,
+ MethodAccessFlags accessFlags) {
+ return appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.NON_STARTUP_IN_STARTUP_OUTLINE,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ .setAccessFlags(accessFlags)
+ .setApiLevelForCode(method.getDefinition().getApiLevelForCode())
+ .setApiLevelForDefinition(method.getDefinition().getApiLevelForDefinition())
+ .setProto(
+ factory.prependHolderToProtoIf(
+ method.getReference(), !method.getAccessFlags().isStatic()))
+ .setCode(
+ syntheticMethod -> {
+ Code code =
+ method
+ .getDefinition()
+ .getCode()
+ .getCodeAsInlining(
+ syntheticMethod,
+ true,
+ method.getReference(),
+ method.getDefinition().isD8R8Synthesized(),
+ factory);
+ if (!method.getAccessFlags().isStatic()) {
+ DexEncodedMethod.setDebugInfoWithFakeThisParameter(
+ code, syntheticMethod.getArity(), appView);
+ }
+ return code;
+ }));
+ }
+
+ private void commitPendingSyntheticClasses() {
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ if (!syntheticItems.hasPendingSyntheticClasses()) {
+ return;
+ }
+ CommittedItems committedItems = syntheticItems.commit(appView.app());
+ if (appView.hasLiveness()) {
+ appView
+ .withLiveness()
+ .setAppInfo(appView.appInfoWithLiveness().rebuildWithLiveness(committedItems));
+ } else {
+ appView
+ .withClassHierarchy()
+ .setAppInfo(appView.appInfo().rebuildWithClassHierarchy(committedItems));
+ }
+ }
+
+ private void setSyntheticKeepInfo() {
+ appView
+ .getKeepInfo()
+ .mutate(
+ keepInfo ->
+ syntheticMethods.forEach(
+ syntheticMethod -> {
+ keepInfo.registerCompilerSynthesizedMethod(syntheticMethod);
+ keepInfo.joinMethod(syntheticMethod, Joiner::disallowInlining);
+ }));
+ }
+
+ private void rewriteWithLens(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ if (lensBuilder.isEmpty()) {
+ return;
+ }
+ NonStartupInStartupOutlinerLens lens = lensBuilder.build(appView);
+ appView.rewriteWithLens(lens, executorService, timing);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerLens.java b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerLens.java
new file mode 100644
index 0000000..086eab6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerLens.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2024, 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.startup;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.NestedGraphLens;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
+
+public class NonStartupInStartupOutlinerLens extends NestedGraphLens {
+
+ public NonStartupInStartupOutlinerLens(
+ AppView<?> appView, BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap) {
+ super(appView, EMPTY_FIELD_MAP, methodMap, EMPTY_TYPE_MAP);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public boolean isNonStartupInStartupOutlinerLens() {
+ return true;
+ }
+
+ @Override
+ protected InvokeType mapInvocationType(
+ DexMethod newMethod, DexMethod previousMethod, InvokeType type) {
+ if (newMethod.isIdenticalTo(previousMethod)) {
+ return type;
+ }
+ return InvokeType.STATIC;
+ }
+
+ public static class Builder {
+
+ private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
+ new BidirectionalOneToOneHashMap<>();
+
+ public boolean isEmpty() {
+ return newMethodSignatures.isEmpty();
+ }
+
+ public synchronized void recordMove(ProgramMethod from, ProgramMethod to) {
+ newMethodSignatures.put(from.getReference(), to.getReference());
+ }
+
+ public NonStartupInStartupOutlinerLens build(
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return new NonStartupInStartupOutlinerLens(appView, newMethodSignatures);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 1ab00b6..0030cec 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -102,6 +102,8 @@
generator.forSingleMethod("ApiModelOutline");
public final SyntheticKind DESUGARED_LIBRARY_BRIDGE =
generator.forSingleMethod("DesugaredLibraryBridge");
+ public final SyntheticKind NON_STARTUP_IN_STARTUP_OUTLINE =
+ generator.forSingleMethodWithGlobalMerging("NonStartupInStartupOutline");
private final List<SyntheticKind> ALL_KINDS;
private String lazyVersionHash = null;
diff --git a/src/test/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerTest.java b/src/test/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerTest.java
new file mode 100644
index 0000000..535a69c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerTest.java
@@ -0,0 +1,206 @@
+// Copyright (c) 2024, 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.startup;
+
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticNonStartupInStartupOutlineClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoAccessModification;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Lists;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonStartupInStartupOutlinerTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(apiLevelWithNativeMultiDexSupport())
+ .build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ List<ExternalStartupItem> startupProfile =
+ Lists.newArrayList(
+ ExternalStartupClass.builder()
+ .setClassReference(Reference.classFromClass(StartupMain.class))
+ .build(),
+ ExternalStartupMethod.builder()
+ .setMethodReference(MethodReferenceUtils.mainMethod(StartupMain.class))
+ .build());
+
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRules(StartupMain.class, NonStartupMain.class)
+ .addKeepRules(
+ "-keepclassmembers class " + StartupMain.class.getTypeName() + " {",
+ " void outlinePinnedInstance();",
+ " void outlinePinnedStatic();",
+ "}")
+ .addOptionsModification(options -> options.getStartupOptions().setEnableOutlining(true))
+ .apply(StartupTestingUtils.addStartupProfile(startupProfile))
+ .allowDiagnosticInfoMessages()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoAccessModificationAnnotationsForMembers()
+ .enableNoMethodStaticizingAnnotations()
+ // To allow inspecting the individual outline classes.
+ .noHorizontalClassMergingOfSynthetics()
+ .setMinApi(parameters)
+ .compile()
+ .inspectMultiDex(this::inspectPrimaryDex, this::inspectSecondaryDex);
+
+ compileResult
+ .run(parameters.getRuntime(), StartupMain.class)
+ .assertSuccessWithOutputLines("main");
+
+ compileResult
+ .run(parameters.getRuntime(), NonStartupMain.class)
+ .assertSuccessWithOutputLines(
+ "movePrivate", "moveStatic", "outlinePinnedInstance", "outlinePinnedStatic");
+ }
+
+ private void inspectPrimaryDex(CodeInspector inspector) {
+ assertEquals(1, inspector.allClasses().size());
+
+ ClassSubject startupMainClassSubject = inspector.clazz(StartupMain.class);
+ assertThat(startupMainClassSubject, isPresent());
+ assertEquals(
+ parameters.canInitNewInstanceUsingSuperclassConstructor() ? 4 : 3,
+ startupMainClassSubject.allMethods().size());
+
+ assertThat(startupMainClassSubject.mainMethod(), isPresent());
+ assertThat(startupMainClassSubject.init(), isAbsent());
+ assertThat(startupMainClassSubject.uniqueMethodWithOriginalName("movePrivate"), isAbsent());
+ assertThat(
+ startupMainClassSubject.uniqueMethodWithOriginalName("movePrivateAccessor"), isPresent());
+ assertThat(startupMainClassSubject.uniqueMethodWithOriginalName("moveStatic"), isAbsent());
+ assertThat(
+ startupMainClassSubject.uniqueMethodWithOriginalName("outlinePinnedInstance"), isPresent());
+ assertThat(
+ startupMainClassSubject.uniqueMethodWithOriginalName("outlinePinnedStatic"), isPresent());
+ }
+
+ private void inspectSecondaryDex(CodeInspector inspector) {
+ assertThat(inspector.clazz(NonStartupMain.class), isPresent());
+
+ ClassSubject movePrivateOutline =
+ inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 0));
+ assertThat(movePrivateOutline, isPresent());
+ assertTrue(
+ movePrivateOutline
+ .uniqueMethod()
+ .streamInstructions()
+ .anyMatch(i -> i.isConstString("movePrivate")));
+
+ ClassSubject outlinePinnedStaticOutline =
+ inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 1));
+ assertThat(
+ inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 1)),
+ isPresent());
+ assertTrue(
+ outlinePinnedStaticOutline
+ .uniqueMethod()
+ .streamInstructions()
+ .anyMatch(i -> i.isConstString("outlinePinnedStatic")));
+
+ ClassSubject outlinePinnedInstanceOutline =
+ inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 2));
+ assertThat(outlinePinnedInstanceOutline, isPresent());
+ assertTrue(
+ outlinePinnedInstanceOutline
+ .uniqueMethod()
+ .streamInstructions()
+ .anyMatch(i -> i.isConstString("outlinePinnedInstance")));
+
+ ClassSubject moveStaticOutline =
+ inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 3));
+ assertThat(moveStaticOutline, isPresent());
+ assertTrue(
+ moveStaticOutline
+ .uniqueMethod()
+ .streamInstructions()
+ .anyMatch(i -> i.isConstString("moveStatic")));
+
+ assertThat(
+ inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 4)),
+ isAbsent());
+ }
+
+ @NeverClassInline
+ static class StartupMain {
+
+ public static void main(String[] args) {
+ System.out.println("main");
+ }
+
+ // Moved to synthetic non-startup class.
+ @NeverInline
+ @NoAccessModification
+ @NoMethodStaticizing
+ private void movePrivate() {
+ System.out.println("movePrivate");
+ }
+
+ @NeverInline
+ @NoMethodStaticizing
+ void movePrivateAccessor() {
+ movePrivate();
+ }
+
+ // Moved to synthetic non-startup class.
+ @NeverInline
+ static void moveStatic() {
+ System.out.println("moveStatic");
+ }
+
+ void outlinePinnedInstance() {
+ System.out.println("outlinePinnedInstance");
+ }
+
+ static void outlinePinnedStatic() {
+ System.out.println("outlinePinnedStatic");
+ }
+ }
+
+ static class NonStartupMain {
+
+ public static void main(String[] args) {
+ new StartupMain().movePrivateAccessor();
+ StartupMain.moveStatic();
+ new StartupMain().outlinePinnedInstance();
+ StartupMain.outlinePinnedStatic();
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index b1744ee..de1aec9 100644
--- a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -227,6 +227,15 @@
originalMethod.getMethodDescriptor());
}
+ public static ClassReference syntheticNonStartupInStartupOutlineClass(Class<?> clazz, int id) {
+ return syntheticNonStartupInStartupOutlineClass(Reference.classFromClass(clazz), id);
+ }
+
+ public static ClassReference syntheticNonStartupInStartupOutlineClass(
+ ClassReference reference, int id) {
+ return syntheticClass(reference, naming.NON_STARTUP_IN_STARTUP_OUTLINE, id);
+ }
+
public static MethodReference syntheticPrivateInterfaceMethodAsCompanionMethod(Method method) {
MethodReference originalMethod = Reference.methodFromMethod(method);
ClassReference companionClassReference =