Skip arguments to Composable functions for optimizing bitwise operations
Change-Id: I6438072788e3d76abc66c6c9fc682a4d1f292bc4
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index a5fe760..af882b2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.SeedMapper;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
+import com.android.tools.r8.optimize.compose.ComposeReferences;
import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
import com.android.tools.r8.profile.art.ArtProfileCollection;
import com.android.tools.r8.profile.startup.profile.StartupProfile;
@@ -105,7 +106,9 @@
// Currently however the liveness may be downgraded thus loosing the computed keep info.
private KeepInfoCollection keepInfo = null;
- private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
+ private ComposeReferences composeReferences = null;
+
+ private final AbstractValueFactory abstractValueFactory;
private final AbstractValueConstantPropagationJoiner abstractValueConstantPropagationJoiner;
private final AbstractValueFieldJoiner abstractValueFieldJoiner;
private final AbstractValueParameterJoiner abstractValueParameterJoiner;
@@ -180,6 +183,7 @@
timing.time(
"Compilation context", () -> CompilationContext.createInitialContext(options()));
this.wholeProgramOptimizations = wholeProgramOptimizations;
+ abstractValueFactory = new AbstractValueFactory(options());
abstractValueConstantPropagationJoiner = new AbstractValueConstantPropagationJoiner(this);
if (enableWholeProgramOptimizations()) {
abstractValueFieldJoiner = new AbstractValueFieldJoiner(withClassHierarchy());
@@ -510,6 +514,14 @@
return appInfo.dexItemFactory();
}
+ public ComposeReferences getComposeReferences() {
+ assert testing().modelUnknownChangedAndDefaultArgumentsToComposableFunctions;
+ if (composeReferences == null) {
+ composeReferences = new ComposeReferences(dexItemFactory());
+ }
+ return composeReferences;
+ }
+
public boolean enableWholeProgramOptimizations() {
return wholeProgramOptimizations == WholeProgramOptimizations.ON;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 78beee1..efe3ead 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -51,6 +51,10 @@
return reference.apply(type -> TYPE, field -> FIELD, method -> METHOD);
}
+ public boolean isMethod() {
+ return this == METHOD;
+ }
+
public boolean isParameter() {
return this == PARAMETER;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 2dbb047..79eca14 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -14,17 +14,26 @@
import com.android.tools.r8.ir.analysis.value.objectstate.KnownLengthArrayState;
import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import java.util.concurrent.ConcurrentHashMap;
public class AbstractValueFactory {
- private ConcurrentHashMap<DexType, SingleConstClassValue> singleConstClassValues =
+ private final TestingOptions testingOptions;
+
+ private final ConcurrentHashMap<DexType, SingleConstClassValue> singleConstClassValues =
new ConcurrentHashMap<>();
- private ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues = new ConcurrentHashMap<>();
- private ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
+ private final ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues =
new ConcurrentHashMap<>();
- private ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
+ private final ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
+ new ConcurrentHashMap<>();
+
+ public AbstractValueFactory(InternalOptions options) {
+ testingOptions = options.testing;
+ }
public SingleBoxedBooleanValue createBoxedBooleanFalse() {
return SingleBoxedBooleanValue.getFalseInstance();
@@ -71,10 +80,20 @@
int definitelySetBits, int definitelyUnsetBits) {
if (definitelySetBits != 0 || definitelyUnsetBits != 0) {
// If all bits are known, then create a single number value.
- if ((definitelySetBits | definitelyUnsetBits) == ALL_BITS_SET_MASK) {
+ boolean allBitsSet = (definitelySetBits | definitelyUnsetBits) == ALL_BITS_SET_MASK;
+ // Account for the temporary hack in the Compose modeling where we create a
+ // DefiniteBitsNumberValue with set bits=0b1^32 and unset bits = 0b1^(31)0. This value is used
+ // to simulate the effect of `x | 1` in joins.
+ if (testingOptions.modelUnknownChangedAndDefaultArgumentsToComposableFunctions) {
+ boolean overlappingSetAndUnsetBits = (definitelySetBits & definitelyUnsetBits) != 0;
+ if (overlappingSetAndUnsetBits) {
+ allBitsSet = false;
+ }
+ }
+ if (allBitsSet) {
return createUncheckedSingleNumberValue(definitelySetBits);
}
- return new DefiniteBitsNumberValue(definitelySetBits, definitelyUnsetBits);
+ return new DefiniteBitsNumberValue(definitelySetBits, definitelyUnsetBits, testingOptions);
}
return AbstractValue.unknown();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
index 778e61b..a18281e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.android.tools.r8.utils.OptionalBool;
import java.util.Objects;
@@ -16,8 +17,10 @@
private final int definitelySetBits;
private final int definitelyUnsetBits;
- public DefiniteBitsNumberValue(int definitelySetBits, int definitelyUnsetBits) {
- assert (definitelySetBits & definitelyUnsetBits) == 0;
+ public DefiniteBitsNumberValue(
+ int definitelySetBits, int definitelyUnsetBits, TestingOptions testingOptions) {
+ assert (definitelySetBits & definitelyUnsetBits) == 0
+ || testingOptions.modelUnknownChangedAndDefaultArgumentsToComposableFunctions;
this.definitelySetBits = definitelySetBits;
this.definitelyUnsetBits = definitelyUnsetBits;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 003b119..6011eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -788,10 +788,26 @@
boolean isConstant = definition != null && definition.isConstNumber();
if (isConstant || hasLocalInfo()) {
builder.append("(");
- if (isConstant && definition.asConstNumber().outValue != null) {
+ if (isConstant && definition.asConstNumber().hasOutValue()) {
ConstNumber constNumber = definition.asConstNumber();
if (constNumber.getOutType().isSinglePrimitive()) {
- builder.append((int) constNumber.getRawValue());
+ Value constNumberValue = constNumber.outValue();
+ int intValue = (int) constNumber.getRawValue();
+ boolean useBinaryRepresentation =
+ !constNumberValue.hasPhiUsers()
+ && constNumberValue.uniqueUsers().stream()
+ .allMatch(
+ user ->
+ user.isAnd()
+ || user.isOr()
+ || ((user.isShl() || user.isShr() || user.isUshr())
+ && constNumberValue == user.asShl().getFirstOperand())
+ || user.isXor());
+ if (useBinaryRepresentation) {
+ builder.append("0b").append(Integer.toBinaryString(intValue));
+ } else {
+ builder.append(intValue);
+ }
} else {
builder.append(constNumber.getRawValue());
}
@@ -823,6 +839,12 @@
return isConstant() && getConstInstruction().isConstNumber();
}
+ public boolean isConstNumber(long rawValue) {
+ return isConstant()
+ && getConstInstruction().isConstNumber()
+ && getConstInstruction().asConstNumber().getRawValue() == rawValue;
+ }
+
public boolean isConstBoolean(boolean value) {
return isConstNumber()
&& definition.asConstNumber().getRawValue() == BooleanUtils.longValue(value);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 3a6e882..49d6400 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -67,6 +67,8 @@
private final AppView<AppInfoWithLiveness> appView;
+ private final ArgumentPropagatorCodeScannerModeling modeling;
+
private final MethodParameterFactory methodParameterFactory = new MethodParameterFactory();
private final Set<DexMethod> monomorphicVirtualMethods = Sets.newIdentityHashSet();
@@ -91,6 +93,7 @@
AppView<AppInfoWithLiveness> appView,
ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection) {
this.appView = appView;
+ this.modeling = new ArgumentPropagatorCodeScannerModeling(appView);
this.reprocessingCriteriaCollection = reprocessingCriteriaCollection;
}
@@ -404,11 +407,11 @@
parameterStates.add(
computeParameterStateForNonReceiver(
invoke,
+ singleTarget,
argumentIndex,
invoke.getArgument(argumentIndex),
context,
- existingMethodState,
- methodReprocessingCriteria.getParameterReprocessingCriteria(argumentIndex)));
+ existingMethodState));
}
// We simulate that the return value is used for methods with void return type. This ensures
@@ -448,11 +451,18 @@
@SuppressWarnings("UnusedVariable")
private ParameterState computeParameterStateForNonReceiver(
InvokeMethod invoke,
+ ProgramMethod singleTarget,
int argumentIndex,
Value argument,
ProgramMethod context,
- ConcreteMonomorphicMethodStateOrBottom existingMethodState,
- ParameterReprocessingCriteria parameterReprocessingCriteria) {
+ ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
+ ParameterState modeledState =
+ modeling.modelParameterStateForArgumentToFunction(
+ invoke, singleTarget, argumentIndex, argument);
+ if (modeledState != null) {
+ return modeledState;
+ }
+
// Don't compute a state for this parameter if the stored state is already unknown.
if (existingMethodState.isMonomorphic()
&& existingMethodState.asMonomorphic().getParameterState(argumentIndex).isUnknown()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
new file mode 100644
index 0000000..7ab506e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.argumentpropagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.optimize.compose.ArgumentPropagatorComposeModeling;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ArgumentPropagatorCodeScannerModeling {
+
+ private final ArgumentPropagatorComposeModeling composeModeling;
+
+ ArgumentPropagatorCodeScannerModeling(AppView<AppInfoWithLiveness> appView) {
+ this.composeModeling =
+ appView.testing().modelUnknownChangedAndDefaultArgumentsToComposableFunctions
+ ? new ArgumentPropagatorComposeModeling(appView)
+ : null;
+ }
+
+ ParameterState modelParameterStateForArgumentToFunction(
+ InvokeMethod invoke, ProgramMethod singleTarget, int argumentIndex, Value argument) {
+ if (composeModeling != null) {
+ return composeModeling.modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
+ invoke, singleTarget, argumentIndex, argument);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
new file mode 100644
index 0000000..4bd4242
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
@@ -0,0 +1,180 @@
+// Copyright (c) 2023, 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.compose;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Or;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BitUtils;
+import com.android.tools.r8.utils.BooleanUtils;
+
+public class ArgumentPropagatorComposeModeling {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final ComposeReferences composeReferences;
+
+ public ArgumentPropagatorComposeModeling(AppView<AppInfoWithLiveness> appView) {
+ assert appView.testing().modelUnknownChangedAndDefaultArgumentsToComposableFunctions;
+ this.appView = appView;
+ this.composeReferences = appView.getComposeReferences();
+ }
+
+ /**
+ * Models calls to @Composable functions from Compose restart lambdas.
+ *
+ * <p>The @Composable functions are static and should have one of the following two parameter
+ * lists:
+ *
+ * <ol>
+ * <li>(..., Composable, int)
+ * <li>(..., Composable, int, int)
+ * </ol>
+ *
+ * <p>The int argument after the Composable parameter is the $$changed parameter. The second int
+ * argument after the Composable parameter (if present) is the $$default parameter.
+ *
+ * <p>The call to a @Composable function from its restart lambda follows the following code
+ * pattern:
+ *
+ * <pre>
+ * MyComposableFunction(
+ * ..., this.composer, updateChangedFlags(this.$$changed) || 1, this.$$default)
+ * </pre>
+ *
+ * <p>The modeling performed by this method assumes that updateChangedFlags() does not have any
+ * impact on $$changed (see the current implementation below). The modeling also assumes that
+ * this.$$changed and this.$$default are captures of the $$changed and $$default parameters of
+ * the @Composable function.
+ *
+ * <pre>
+ * internal fun updateChangedFlags(flags: Int): Int {
+ * val lowBits = flags and changedLowBitMask
+ * val highBits = flags and changedHighBitMask
+ * return ((flags and changedMask) or
+ * (lowBits or (highBits shr 1)) or ((lowBits shl 1) and highBits))
+ * }
+ * </pre>
+ */
+ public ParameterState modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
+ InvokeMethod invoke, ProgramMethod singleTarget, int argumentIndex, Value argument) {
+ // First check if this is an invoke to a @Composable function.
+ if (singleTarget == null
+ || singleTarget
+ .getDefinition()
+ .annotations()
+ .getFirstMatching(composeReferences.composableType)
+ == null) {
+ return null;
+ }
+
+ // The @Composable function is expected to be static and have >= 2 parameters.
+ if (!invoke.isInvokeStatic()) {
+ return null;
+ }
+
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod.getArity() < 2) {
+ return null;
+ }
+
+ // Check if the parameters list is one of (..., Composer, int) or (..., Composer, int, int).
+ if (!invokedMethod.getParameter(invokedMethod.getArity() - 1).isIntType()) {
+ return null;
+ }
+
+ boolean hasDefaultParameter =
+ invokedMethod.getParameter(invokedMethod.getArity() - 2).isIntType();
+ if (hasDefaultParameter && invokedMethod.getArity() < 3) {
+ return null;
+ }
+
+ int composerParameterIndex =
+ invokedMethod.getArity() - 2 - BooleanUtils.intValue(hasDefaultParameter);
+ if (!invokedMethod
+ .getParameter(composerParameterIndex)
+ .isIdenticalTo(composeReferences.composerType)) {
+ return null;
+ }
+
+ // We only model the two last arguments ($$changed and $$default) to the @Composable function.
+ // If this argument is not on of these two int arguments, then don't apply any modeling.
+ if (argumentIndex <= composerParameterIndex) {
+ return null;
+ }
+
+ assert argument.getType().isInt();
+
+ DexString expectedFieldName;
+ ParameterState state = ParameterState.bottomPrimitiveTypeParameter();
+ if (!hasDefaultParameter || argumentIndex == invokedMethod.getArity() - 2) {
+ // We are looking at an argument to the $$changed parameter of the @Composable function.
+ // We generally expect this argument to be defined by a call to updateChangedFlags().
+ if (argument.isDefinedByInstructionSatisfying(Instruction::isInvokeStatic)) {
+ InvokeStatic invokeStatic = argument.getDefinition().asInvokeStatic();
+ DexMethod maybeUpdateChangedFlagsMethod =
+ appView
+ .graphLens()
+ .getOriginalMethodSignature(
+ invokeStatic.getInvokedMethod(), GraphLens.getIdentityLens());
+ if (!maybeUpdateChangedFlagsMethod.isIdenticalTo(
+ composeReferences.updatedChangedFlagsMethod)) {
+ return null;
+ }
+ // Assume the call does not impact the $$changed capture and strip the call.
+ argument = invokeStatic.getFirstArgument();
+ }
+ // Allow the argument to be defined by `this.$$changed | 1`.
+ if (argument.isDefinedByInstructionSatisfying(Instruction::isOr)) {
+ Or or = argument.getDefinition().asOr();
+ Value maybeNumberOperand =
+ or.leftValue().isConstNumber() ? or.leftValue() : or.rightValue();
+ Value otherOperand = or.getOperand(1 - or.inValues().indexOf(maybeNumberOperand));
+ if (!maybeNumberOperand.isConstNumber(1)) {
+ return null;
+ }
+ // Strip the OR instruction.
+ argument = otherOperand;
+ // Update the model from bottom to a special value that effectively throws away any known
+ // information about the lowermost bit of $$changed.
+ state =
+ new ConcretePrimitiveTypeParameterState(
+ appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(
+ BitUtils.ALL_BITS_SET_MASK, BitUtils.ALL_BITS_SET_MASK << 1));
+ }
+ expectedFieldName = composeReferences.changedFieldName;
+ } else {
+ // We are looking at an argument to the $$default parameter of the @Composable function.
+ expectedFieldName = composeReferences.defaultFieldName;
+ }
+
+ // At this point we expect that the restart lambda is reading either this.$$changed or
+ // this.$$default using an instance-get.
+ if (!argument.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) {
+ return null;
+ }
+
+ // Check that the instance-get is reading the capture field that we expect it to.
+ InstanceGet instanceGet = argument.getDefinition().asInstanceGet();
+ if (!instanceGet.getField().getName().isIdenticalTo(expectedFieldName)) {
+ return null;
+ }
+
+ // Return the argument model. Note that, for the $$default field, this is always bottom, which
+ // is equivalent to modeling that this call does not contribute any new argument information.
+ return state;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
new file mode 100644
index 0000000..7a489a7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, 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.compose;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+
+public class ComposeReferences {
+
+ public final DexString changedFieldName;
+ public final DexString defaultFieldName;
+
+ public final DexType composableType;
+ public final DexType composerType;
+
+ public final DexMethod updatedChangedFlagsMethod;
+
+ public ComposeReferences(DexItemFactory factory) {
+ changedFieldName = factory.createString("$$changed");
+ defaultFieldName = factory.createString("$$default");
+
+ composableType = factory.createType("Landroidx/compose/runtime/Composable;");
+ composerType = factory.createType("Landroidx/compose/runtime/Composer;");
+
+ updatedChangedFlagsMethod =
+ factory.createMethod(
+ factory.createType("Landroidx/compose/runtime/RecomposeScopeImplKt;"),
+ factory.createProto(factory.intType, factory.intType),
+ "updateChangedFlags");
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysisHelper.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysisHelper.java
index e06916b..5416619 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysisHelper.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysisHelper.java
@@ -92,7 +92,7 @@
assert false;
}
} else {
- assert false;
+ assert array.isNullType();
}
},
options);
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 6389e26..a02420c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -62,7 +62,7 @@
ProgramDefinition holder, DexAnnotation annotation, AnnotatedKind kind) {
return annotationsToRetain.contains(annotation)
|| shouldKeepAnnotation(
- appView, holder, annotation, isAnnotationTypeLive(annotation), kind);
+ appView, holder, annotation, isAnnotationTypeLive(annotation), kind, mode);
}
public static boolean shouldKeepAnnotation(
@@ -70,13 +70,14 @@
ProgramDefinition holder,
DexAnnotation annotation,
boolean isAnnotationTypeLive,
- AnnotatedKind kind) {
+ AnnotatedKind kind,
+ Mode mode) {
// If we cannot run the AnnotationRemover we are keeping the annotation.
- if (!appView.options().isShrinking()) {
+ InternalOptions options = appView.options();
+ if (!options.isShrinking()) {
return true;
}
- InternalOptions options = appView.options();
ProguardKeepAttributes config =
options.getProguardConfiguration() != null
? options.getProguardConfiguration().getKeepAttributes()
@@ -145,6 +146,9 @@
.startsWith(options.itemFactory.dalvikAnnotationOptimizationPrefix)) {
return true;
}
+ if (isComposableAnnotationToRetain(appView, annotation, kind, mode, options)) {
+ return true;
+ }
if (kind.isParameter()) {
if (!options.isKeepRuntimeInvisibleParameterAnnotationsEnabled()) {
return false;
@@ -291,14 +295,10 @@
boolean isAnnotation =
definition.isProgramClass() && definition.asProgramClass().isAnnotation();
if (keepInfo.isAnnotationRemovalAllowed(options)) {
- if (isAnnotation) {
+ if (isAnnotation || mode.isInitialTreeShaking()) {
definition.rewriteAllAnnotations(
- (annotation, isParameterAnnotation) ->
- shouldRetainAnnotationOnAnnotationClass(annotation) ? annotation : null);
- } else if (mode.isInitialTreeShaking()) {
- definition.rewriteAllAnnotations(
- (annotation, isParameterAnnotation) ->
- annotationsToRetain.contains(annotation) ? annotation : null);
+ (annotation, kind) ->
+ shouldRetainAnnotation(definition, annotation, kind) ? annotation : null);
} else {
definition.clearAllAnnotations();
}
@@ -308,14 +308,34 @@
}
}
- private boolean shouldRetainAnnotationOnAnnotationClass(DexAnnotation annotation) {
- if (DexAnnotation.isAnnotationDefaultAnnotation(annotation, appView.dexItemFactory())) {
- return shouldRetainAnnotationDefaultAnnotationOnAnnotationClass(annotation);
+ private boolean shouldRetainAnnotation(
+ ProgramDefinition definition, DexAnnotation annotation, AnnotatedKind kind) {
+ boolean isAnnotationOnAnnotationClass =
+ definition.isProgramClass() && definition.asProgramClass().isAnnotation();
+ if (isAnnotationOnAnnotationClass) {
+ if (DexAnnotation.isAnnotationDefaultAnnotation(annotation, appView.dexItemFactory())) {
+ return shouldRetainAnnotationDefaultAnnotationOnAnnotationClass(annotation);
+ }
+ if (DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())) {
+ return shouldRetainRetentionAnnotationOnAnnotationClass(annotation);
+ }
}
- if (DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())) {
- return shouldRetainRetentionAnnotationOnAnnotationClass(annotation);
- }
- return annotationsToRetain.contains(annotation);
+ return annotationsToRetain.contains(annotation)
+ || isComposableAnnotationToRetain(appView, annotation, kind, mode, options);
+ }
+
+ private static boolean isComposableAnnotationToRetain(
+ AppView<?> appView,
+ DexAnnotation annotation,
+ AnnotatedKind kind,
+ Mode mode,
+ InternalOptions options) {
+ return options.testing.modelUnknownChangedAndDefaultArgumentsToComposableFunctions
+ && mode.isInitialTreeShaking()
+ && kind.isMethod()
+ && annotation
+ .getAnnotationType()
+ .isIdenticalTo(appView.getComposeReferences().composableType);
}
@SuppressWarnings("UnusedVariable")
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d07235f..fda3ddf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2347,7 +2347,7 @@
return true;
}
return AnnotationRemover.shouldKeepAnnotation(
- appView, annotatedItem, annotation, isLive, annotatedKind);
+ appView, annotatedItem, annotation, isLive, annotatedKind, mode);
}
private DexProgramClass resolveBaseType(DexType type, ProgramDefinition context) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 1367e23..51593eb 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2398,6 +2398,10 @@
System.getProperty("com.android.tools.r8.disableMarkingClassesFinal") != null;
public boolean testEnableTestAssertions = false;
public boolean keepMetadataInR8IfNotRewritten = true;
+ public boolean modelUnknownChangedAndDefaultArgumentsToComposableFunctions =
+ SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault(
+ "com.android.tools.r8.modelUnknownChangedAndDefaultArgumentsToComposableFunctions",
+ false);
// Flag to allow processing of resources in D8. A data resource consumer still needs to be
// specified.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
index 7cd720a..556a914 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.utils.InternalOptions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -127,8 +128,9 @@
@Test
public void testCombineRemoveRewritten() {
- DexItemFactory factory = new DexItemFactory();
- AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
+ InternalOptions options = new InternalOptions();
+ DexItemFactory factory = options.dexItemFactory();
+ AbstractValueFactory abstractValueFactory = new AbstractValueFactory(options);
ArgumentInfoCollection.Builder builder1 = ArgumentInfoCollection.builder();
builder1.addArgumentInfo(