Merge commit 'd6d30aa0fa15ac2771972a55d0fd8e86d3c1a251' into dev-release
Change-Id: I5a7c07ae3c1678f255e6875613024d5d83505f68
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index a081d34..4869bc5 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -188,7 +188,7 @@
while (iterator.hasNext()) {
result = result.join(toTypeElement(iterator.next()), appView);
}
- // All types are reference types so the join is either a class or an array.
+ // All types are reference types so the join is either a class, an interface or an array.
if (result.isClassType()) {
ClassTypeElement classType = result.asClassType();
if (classType.getClassType().isIdenticalTo(appView.dexItemFactory().objectType)
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index c16eae4..9936ef6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -31,9 +31,10 @@
import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.HashingVisitor;
-import java.util.ArrayList;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.List;
import java.util.ListIterator;
+import java.util.function.Supplier;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.MethodVisitor;
@@ -41,22 +42,8 @@
private final ConstantDynamicReference reference;
- public CfConstDynamic(
- int symbolicReferenceId,
- DexString name,
- DexType type,
- DexMethodHandle bootstrapMethod,
- List<DexValue> bootstrapMethodArguments) {
- assert symbolicReferenceId >= 0;
- assert name != null;
- assert type != null;
- assert bootstrapMethod != null;
- assert bootstrapMethodArguments != null;
- assert bootstrapMethodArguments.isEmpty();
-
- reference =
- new ConstantDynamicReference(
- symbolicReferenceId, name, type, bootstrapMethod, bootstrapMethodArguments);
+ public CfConstDynamic(ConstantDynamicReference constantDynamicReference) {
+ reference = constantDynamicReference;
}
@Override
@@ -86,27 +73,14 @@
}
public static CfConstDynamic fromAsmConstantDynamic(
- int symbolicReferenceId,
ConstantDynamic insn,
JarApplicationReader application,
- DexType clazz) {
- String constantName = insn.getName();
- String constantDescriptor = insn.getDescriptor();
- DexMethodHandle bootstrapMethodHandle =
- DexMethodHandle.fromAsmHandle(insn.getBootstrapMethod(), application, clazz);
- int argumentCount = insn.getBootstrapMethodArgumentCount();
- List<DexValue> bootstrapMethodArguments = new ArrayList<>(argumentCount);
- for (int i = 0; i < argumentCount; i++) {
- Object argument = insn.getBootstrapMethodArgument(i);
- DexValue dexValue = DexValue.fromAsmBootstrapArgument(argument, application, clazz);
- bootstrapMethodArguments.add(dexValue);
- }
+ DexType clazz,
+ Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
+ assert insn.getBootstrapMethodArgumentCount() == 0;
return new CfConstDynamic(
- symbolicReferenceId,
- application.getString(constantName),
- application.getTypeFromDescriptor(constantDescriptor),
- bootstrapMethodHandle,
- bootstrapMethodArguments);
+ ConstantDynamicReference.fromAsmConstantDynamic(
+ insn, application, clazz, constantDynamicSymbolicReferencesSupplier));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
index d1b3d3e..86ea3c0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -248,29 +248,32 @@
// safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
// by replacing the operand stack with a stack whose sole element is the handler's
// exception class.
- for (CfLabel target : tryCatchRange.getTargets()) {
+ List<CfLabel> targets = tryCatchRange.getTargets();
+ for (int i = 0; i < targets.size(); i++) {
+ CfLabel target = targets.get(i);
+ DexType guard = tryCatchRange.guards.get(i);
CfFrame destinationFrame = labelToFrameMap.get(target);
if (destinationFrame == null) {
return CfCodeStackMapValidatingException.invalidTryCatchRange(
method, tryCatchRange, "No frame for target catch range target", appView);
}
- // From the spec: the handler's exception class is assignable to the class Throwable.
- for (DexType guard : tryCatchRange.guards) {
- if (!config.getAssignability().isAssignable(guard, factory.throwableType)) {
- return CfCodeStackMapValidatingException.invalidTryCatchRange(
- method,
- tryCatchRange,
- "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
- appView);
- }
- Deque<PreciseFrameType> sourceStack =
- ImmutableDeque.of(FrameType.initializedNonNullReference(guard));
- AssignabilityResult assignabilityResult =
- config.getAssignability().isStackAssignable(sourceStack, destinationFrame.getStack());
- if (assignabilityResult.isFailed()) {
- return CfCodeStackMapValidatingException.invalidTryCatchRange(
- method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
- }
+ Deque<PreciseFrameType> sourceStack =
+ ImmutableDeque.of(FrameType.initializedNonNullReference(guard));
+ AssignabilityResult assignabilityResult =
+ config.getAssignability().isStackAssignable(sourceStack, destinationFrame.getStack());
+ if (assignabilityResult.isFailed()) {
+ return CfCodeStackMapValidatingException.invalidTryCatchRange(
+ method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
+ }
+ }
+ // From the spec: the handler's exception class is assignable to the class Throwable.
+ for (DexType guard : tryCatchRange.guards) {
+ if (!config.getAssignability().isAssignable(guard, factory.throwableType)) {
+ return CfCodeStackMapValidatingException.invalidTryCatchRange(
+ method,
+ tryCatchRange,
+ "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
+ appView);
}
}
return null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index c09e32a..2cf72f1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import com.google.common.io.BaseEncoding;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
@@ -24,9 +25,10 @@
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Supplier;
+import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.InvokeDynamicInsnNode;
public final class DexCallSite extends IndexedDexItem
implements StructuralItem<DexCallSite>, LirConstant {
@@ -76,17 +78,13 @@
}
public static DexCallSite fromAsmInvokeDynamic(
- InvokeDynamicInsnNode insn, JarApplicationReader application, DexType clazz) {
- return fromAsmInvokeDynamic(application, clazz, insn.name, insn.desc, insn.bsm, insn.bsmArgs);
- }
-
- public static DexCallSite fromAsmInvokeDynamic(
JarApplicationReader application,
DexType clazz,
String name,
String desc,
Handle bsmHandle,
- Object[] bsmArgs) {
+ Object[] bsmArgs,
+ Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
// Bootstrap method
if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC
&& bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
@@ -99,7 +97,9 @@
// Decode static bootstrap arguments
List<DexValue> bootstrapArgs = new ArrayList<>();
for (Object arg : bsmArgs) {
- bootstrapArgs.add(DexValue.fromAsmBootstrapArgument(arg, application, clazz));
+ bootstrapArgs.add(
+ DexValue.fromAsmBootstrapArgument(
+ arg, application, clazz, constantDynamicSymbolicReferencesSupplier));
}
// Construct call site
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 38b5634..f4f7a60 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.dex.FileWriter;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -19,6 +20,7 @@
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.EncodedValueUtils;
@@ -26,8 +28,11 @@
import com.android.tools.r8.utils.structural.HashingVisitor;
import com.android.tools.r8.utils.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.Arrays;
import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Type;
@@ -51,7 +56,8 @@
ARRAY(0x1c),
ANNOTATION(0x1d),
NULL(0x1e),
- BOOLEAN(0x1f);
+ BOOLEAN(0x1f),
+ CONST_DYNAMIC(-1);
public static DexValueKind fromId(int id) {
switch (id) {
@@ -91,6 +97,8 @@
return NULL;
case 0x1f:
return BOOLEAN;
+ case -1:
+ return CONST_DYNAMIC;
default:
throw new Unreachable();
}
@@ -162,6 +170,14 @@
return null;
}
+ public boolean isDexValueConstDynamic() {
+ return false;
+ }
+
+ public DexValueConstDynamic asDexValueConstDynamic() {
+ return null;
+ }
+
public boolean isDexValueMethodType() {
return false;
}
@@ -313,7 +329,10 @@
public abstract AbstractValue toAbstractValue(AbstractValueFactory factory);
public static DexValue fromAsmBootstrapArgument(
- Object value, JarApplicationReader application, DexType clazz) {
+ Object value,
+ JarApplicationReader application,
+ DexType clazz,
+ Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
if (value instanceof Integer) {
return DexValue.DexValueInt.create((Integer) value);
} else if (value instanceof Long) {
@@ -334,12 +353,23 @@
case Type.METHOD:
return new DexValue.DexValueMethodType(
application.getProto(((Type) value).getDescriptor()));
+ case Type.ARRAY:
+ DexType arrayType = application.getTypeFromDescriptor(((Type) value).getDescriptor());
+ assert arrayType.isArrayType();
+ return new DexValue.DexValueType(arrayType);
default:
throw new Unreachable("Type sort is not supported: " + type.getSort());
}
} else if (value instanceof Handle) {
return new DexValue.DexValueMethodHandle(
DexMethodHandle.fromAsmHandle((Handle) value, application, clazz));
+ } else if (value instanceof ConstantDynamic) {
+ return new DexValue.DexValueConstDynamic(
+ ConstantDynamicReference.fromAsmConstantDynamic(
+ (ConstantDynamic) value,
+ application,
+ clazz,
+ constantDynamicSymbolicReferencesSupplier));
} else {
throw new Unreachable(
"Unsupported bootstrap static argument of type " + value.getClass().getSimpleName());
@@ -2054,4 +2084,96 @@
return UnknownValue.getInstance();
}
}
+
+ public static class DexValueConstDynamic extends DexValue {
+
+ private final ConstantDynamicReference value;
+
+ public DexValueConstDynamic(ConstantDynamicReference value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isDexValueConstDynamic() {
+ return true;
+ }
+
+ @Override
+ public DexValueConstDynamic asDexValueConstDynamic() {
+ return this;
+ }
+
+ @Override
+ int internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+ return value.acceptCompareTo(other.asDexValueConstDynamic().value, visitor);
+ }
+
+ @Override
+ void internalAcceptHashing(HashingVisitor visitor) {
+ value.acceptHashing(visitor);
+ }
+
+ @Override
+ public DexValueKind getValueKind() {
+ return DexValueKind.CONST_DYNAMIC;
+ }
+
+ private CompilationError throwCannotConvertToDex() {
+ throw new CompilationError("DexValueConstDynamic should be desugared");
+ }
+
+ @Override
+ public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
+ throw throwCannotConvertToDex();
+ }
+
+ @Override
+ public void sort() {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
+ throw throwCannotConvertToDex();
+ }
+
+ @Override
+ public int hashCode() {
+ return 7 * value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof DexValueConstDynamic) {
+ DexValueConstDynamic otherCstDynamic = (DexValueConstDynamic) other;
+ return otherCstDynamic.value.equals(value);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Item " + getValueKind() + " " + value;
+ }
+
+ @Override
+ public DexType getType(DexItemFactory factory) {
+ return null;
+ }
+
+ @Override
+ public Object getBoxedValue() {
+ throw new Unreachable("No boxed value for DexValueConstDynamic");
+ }
+
+ @Override
+ public Object asAsmEncodedObject() {
+ throw new Unreachable("No ASM conversion for DexValueConstDynamic");
+ }
+
+ @Override
+ public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+ return UnknownValue.getInstance();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 2ad9817..c41f8df 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -921,7 +921,14 @@
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
DexCallSite callSite =
- DexCallSite.fromAsmInvokeDynamic(application, method.holder, name, desc, bsm, bsmArgs);
+ DexCallSite.fromAsmInvokeDynamic(
+ application,
+ method.holder,
+ name,
+ desc,
+ bsm,
+ bsmArgs,
+ constantDynamicSymbolicReferencesSupplier);
addInstruction(new CfInvokeDynamic(callSite));
}
@@ -1027,16 +1034,12 @@
// instance from ASM, even when they are equal (i.e. all their components are equal). See
// ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest
// for an example.
- Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences =
- constantDynamicSymbolicReferencesSupplier.get();
- int symbolicReferenceId = constantDynamicSymbolicReferences.getOrDefault(cst, -1);
- if (symbolicReferenceId == -1) {
- symbolicReferenceId = constantDynamicSymbolicReferences.size();
- constantDynamicSymbolicReferences.put((ConstantDynamic) cst, symbolicReferenceId);
- }
addInstruction(
CfConstDynamic.fromAsmConstantDynamic(
- symbolicReferenceId, (ConstantDynamic) cst, application, method.holder));
+ (ConstantDynamic) cst,
+ application,
+ method.holder,
+ constantDynamicSymbolicReferencesSupplier));
} else {
throw new CompilationError("Unsupported constant: " + cst.toString());
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
index f70748d..fa4979c 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -25,7 +25,6 @@
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
-import com.android.tools.r8.utils.DescriptorUtils;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.IdentityHashMap;
@@ -36,16 +35,19 @@
private final R8ResourceShrinkerState resourceShrinkerState;
private final Map<DexType, RClassFieldToValueStore> fieldToValueMapping = new IdentityHashMap<>();
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Enqueuer enqueuer;
- private ResourceAccessAnalysis(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ private ResourceAccessAnalysis(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
this.appView = appView;
this.resourceShrinkerState = appView.getResourceShrinkerState();
+ this.enqueuer = enqueuer;
}
public static void register(
AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
if (enabled(appView, enqueuer)) {
- enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView));
+ enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
}
}
@@ -73,7 +75,7 @@
if (resolvedField == null) {
return;
}
- if (getMaybeCachedIsRClass(resolvedField.getHolder())) {
+ if (enqueuer.isRClass(resolvedField.getHolder())) {
DexType holderType = resolvedField.getHolderType();
if (!fieldToValueMapping.containsKey(holderType)) {
populateRClassValues(resolvedField);
@@ -189,18 +191,6 @@
}
}
- private final Map<DexProgramClass, Boolean> cachedClassLookups = new IdentityHashMap<>();
-
- private boolean getMaybeCachedIsRClass(DexProgramClass holder) {
- Boolean result = cachedClassLookups.get(holder);
- if (result != null) {
- return result;
- }
- boolean isRClass = DescriptorUtils.isRClassDescriptor(holder.getType().toDescriptorString());
- cachedClassLookups.put(holder, isRClass);
- return isRClass;
- }
-
private static class RClassFieldToValueStore {
private Map<DexField, IntList> valueMapping;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
index 478e4cf..2f27eab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -7,12 +7,17 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.utils.structural.Equatable;
import com.android.tools.r8.utils.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.function.Supplier;
+import org.objectweb.asm.ConstantDynamic;
public class ConstantDynamicReference implements StructuralItem<ConstantDynamicReference> {
private final int symbolicReferenceId;
@@ -25,13 +30,53 @@
spec.withInt(c -> c.symbolicReferenceId);
}
- public ConstantDynamicReference(
+ public static ConstantDynamicReference fromAsmConstantDynamic(
+ ConstantDynamic cst,
+ JarApplicationReader application,
+ DexType clazz,
+ Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
+
+ Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences =
+ constantDynamicSymbolicReferencesSupplier.get();
+ int symbolicReferenceId = constantDynamicSymbolicReferences.getOrDefault(cst, -1);
+ if (symbolicReferenceId == -1) {
+ symbolicReferenceId = constantDynamicSymbolicReferences.size();
+ constantDynamicSymbolicReferences.put(cst, symbolicReferenceId);
+ }
+
+ String constantName = cst.getName();
+ String constantDescriptor = cst.getDescriptor();
+ DexMethodHandle bootstrapMethodHandle =
+ DexMethodHandle.fromAsmHandle(cst.getBootstrapMethod(), application, clazz);
+ int argumentCount = cst.getBootstrapMethodArgumentCount();
+ List<DexValue> bootstrapMethodArguments1 = new ArrayList<>(argumentCount);
+ for (int i = 0; i < argumentCount; i++) {
+ Object argument = cst.getBootstrapMethodArgument(i);
+ DexValue dexValue =
+ DexValue.fromAsmBootstrapArgument(
+ argument, application, clazz, constantDynamicSymbolicReferencesSupplier);
+ bootstrapMethodArguments1.add(dexValue);
+ }
+
+ return new ConstantDynamicReference(
+ symbolicReferenceId,
+ application.getString(constantName),
+ application.getTypeFromDescriptor(constantDescriptor),
+ bootstrapMethodHandle,
+ bootstrapMethodArguments1);
+ }
+
+ private ConstantDynamicReference(
int symbolicReferenceId,
DexString name,
DexType type,
DexMethodHandle bootstrapMethod,
List<DexValue> bootstrapMethodArguments) {
- assert bootstrapMethodArguments.isEmpty();
+ assert symbolicReferenceId >= 0;
+ assert name != null;
+ assert type != null;
+ assert bootstrapMethod != null;
+ assert bootstrapMethodArguments != null;
this.symbolicReferenceId = symbolicReferenceId;
this.name = name;
this.type = type;
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 1f95169..7ee35d8 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
@@ -774,6 +774,10 @@
return resolvedMethod.getReference();
}
+ if (isMonomorphicVirtualMethod(resolvedMethod)) {
+ return resolvedMethod.getReference();
+ }
+
if (invoke.isInvokeInterface()) {
assert !isMonomorphicVirtualMethod(resolvedMethod);
return getVirtualRootMethod(resolvedMethod);
@@ -781,10 +785,6 @@
assert invoke.isInvokeSuper() || invoke.isInvokeVirtual();
- if (isMonomorphicVirtualMethod(resolvedMethod)) {
- return resolvedMethod.getReference();
- }
-
DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
assert rootMethod != null;
assert !isMonomorphicVirtualMethod(resolvedMethod)
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 b5f1374..0165f8e 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,9 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
@@ -14,8 +13,13 @@
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;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -27,58 +31,86 @@
protected static class VirtualRootMethod {
private final VirtualRootMethod parent;
- private final ProgramMethod root;
- private final ProgramMethodSet overrides = ProgramMethodSet.create();
+ private final ProgramMethod method;
+
+ private Set<VirtualRootMethod> overrides = Collections.emptySet();
+ private List<VirtualRootMethod> siblings = Collections.emptyList();
+ private boolean mayDispatchOutsideProgram = false;
VirtualRootMethod(ProgramMethod root) {
this(root, null);
}
- VirtualRootMethod(ProgramMethod root, VirtualRootMethod parent) {
- assert root != null;
+ VirtualRootMethod(ProgramMethod method, VirtualRootMethod parent) {
+ assert method != null;
this.parent = parent;
- this.root = root;
+ this.method = method;
}
- void addOverride(ProgramMethod override) {
- assert override.getDefinition() != root.getDefinition();
- assert override.getMethodSignature().equals(root.getMethodSignature());
+ void addOverride(VirtualRootMethod override) {
+ assert !override.getMethod().isStructurallyEqualTo(method);
+ assert override.getMethod().getReference().match(method.getReference());
+ if (overrides.isEmpty()) {
+ overrides = Sets.newIdentityHashSet();
+ }
overrides.add(override);
if (hasParent()) {
getParent().addOverride(override);
}
}
+ void addSibling(VirtualRootMethod sibling) {
+ if (siblings.isEmpty()) {
+ siblings = new ArrayList<>(1);
+ }
+ siblings.add(sibling);
+ }
+
+ void setMayDispatchOutsideProgram() {
+ mayDispatchOutsideProgram = true;
+ }
+
boolean hasParent() {
return parent != null;
}
+ boolean hasSiblings() {
+ return !siblings.isEmpty();
+ }
+
VirtualRootMethod getParent() {
return parent;
}
- ProgramMethod getRoot() {
- return root;
+ VirtualRootMethod getRoot() {
+ return hasParent() ? getParent().getRoot() : this;
}
- ProgramMethod getSingleNonAbstractMethod() {
- ProgramMethod singleNonAbstractMethod = root.getAccessFlags().isAbstract() ? null : root;
- for (ProgramMethod override : overrides) {
- if (!override.getAccessFlags().isAbstract()) {
- if (singleNonAbstractMethod != null) {
+ ProgramMethod getMethod() {
+ return method;
+ }
+
+ VirtualRootMethod getSingleDispatchTarget() {
+ assert !hasParent();
+ if (isMayDispatchOutsideProgramSet()) {
+ return null;
+ }
+ VirtualRootMethod singleDispatchTarget = isAbstract() ? null : this;
+ for (VirtualRootMethod override : overrides) {
+ if (!override.isAbstract()) {
+ if (singleDispatchTarget != null) {
// Not a single non-abstract method.
return null;
}
- singleNonAbstractMethod = override;
+ singleDispatchTarget = override;
}
}
- assert singleNonAbstractMethod == null
- || !singleNonAbstractMethod.getAccessFlags().isAbstract();
- return singleNonAbstractMethod;
+ assert singleDispatchTarget == null || !singleDispatchTarget.isAbstract();
+ return singleDispatchTarget;
}
- void forEach(Consumer<ProgramMethod> consumer) {
- consumer.accept(root);
+ void forEach(Consumer<VirtualRootMethod> consumer) {
+ consumer.accept(this);
overrides.forEach(consumer);
}
@@ -86,10 +118,12 @@
return !overrides.isEmpty();
}
- boolean isInterfaceMethodWithSiblings() {
- // TODO(b/190154391): Conservatively returns true for all interface methods, but should only
- // return true for those with siblings.
- return root.getHolder().isInterface();
+ boolean isAbstract() {
+ return method.getAccessFlags().isAbstract();
+ }
+
+ boolean isMayDispatchOutsideProgramSet() {
+ return mayDispatchOutsideProgram;
}
}
@@ -122,18 +156,48 @@
DexMethodSignatureMap<VirtualRootMethod> virtualRootMethodsForSuperclass =
virtualRootMethodsPerClass.get(superclass);
virtualRootMethodsForSuperclass.forEach(
- (signature, info) ->
- virtualRootMethodsForClass.computeIfAbsent(
- signature, ignoreKey(() -> new VirtualRootMethod(info.getRoot(), info))));
+ (signature, info) -> {
+ virtualRootMethodsForClass.compute(
+ signature,
+ (ignore, existing) -> {
+ if (existing == null || existing == info) {
+ return info;
+ } else {
+ // We iterate the immediate supertypes in-order using
+ // forEachImmediateProgramSuperClass. Therefore, the current method is
+ // guaranteed to be an interface method when existing != null.
+ assert info.getMethod().getHolder().isInterface();
+ if (!existing.getMethod().getHolder().isInterface()) {
+ existing.addSibling(info);
+ info.addOverride(existing);
+ }
+ return existing;
+ }
+ });
+ if (!clazz.isInterface() && superclass.isInterface()) {
+ DexClassAndMethod resolvedMethod =
+ appView.appInfo().resolveMethodOnClass(clazz, signature).getResolutionPair();
+ if (resolvedMethod != null
+ && !resolvedMethod.isProgramMethod()
+ && !resolvedMethod.getAccessFlags().isAbstract()) {
+ info.setMayDispatchOutsideProgram();
+ }
+ }
+ });
});
clazz.forEachProgramVirtualMethod(
- method -> {
- if (virtualRootMethodsForClass.containsKey(method)) {
- virtualRootMethodsForClass.get(method).getParent().addOverride(method);
- } else {
- virtualRootMethodsForClass.put(method, new VirtualRootMethod(method));
- }
- });
+ method ->
+ virtualRootMethodsForClass.compute(
+ method,
+ (ignore, parent) -> {
+ if (parent == null) {
+ return new VirtualRootMethod(method);
+ } else {
+ VirtualRootMethod override = new VirtualRootMethod(method, parent);
+ parent.addOverride(override);
+ return override;
+ }
+ }));
return virtualRootMethodsForClass;
}
@@ -147,37 +211,43 @@
VirtualRootMethod virtualRootMethod =
virtualRootMethodsForClass.remove(rootCandidate.getMethodSignature());
acceptVirtualMethod(rootCandidate, virtualRootMethod);
- if (!rootCandidate.isStructurallyEqualTo(virtualRootMethod.getRoot())) {
+ if (virtualRootMethod.hasParent()
+ || !rootCandidate.isStructurallyEqualTo(virtualRootMethod.getMethod())) {
return;
}
- boolean isMonomorphicVirtualMethod =
- !clazz.isInterface() && !virtualRootMethod.hasOverrides();
- if (isMonomorphicVirtualMethod) {
+ if (!virtualRootMethod.hasOverrides()
+ && !virtualRootMethod.hasSiblings()
+ && !virtualRootMethod.isMayDispatchOutsideProgramSet()) {
monomorphicVirtualRootMethods.add(rootCandidate);
- } else {
- ProgramMethod singleNonAbstractMethod = virtualRootMethod.getSingleNonAbstractMethod();
- if (singleNonAbstractMethod != null
- && !virtualRootMethod.isInterfaceMethodWithSiblings()) {
+ return;
+ }
+ if (!rootCandidate.getHolder().isInterface()) {
+ VirtualRootMethod singleDispatchTarget = virtualRootMethod.getSingleDispatchTarget();
+ if (singleDispatchTarget != null) {
virtualRootMethod.forEach(
- method -> {
- // Interface methods can have siblings and can therefore not be mapped to their
- // unique non-abstract implementation, unless the interface method does not have
- // any siblings.
- virtualRootMethods.put(
- method.getReference(), singleNonAbstractMethod.getReference());
- });
- if (!singleNonAbstractMethod.getHolder().isInterface()) {
- monomorphicVirtualNonRootMethods.add(singleNonAbstractMethod);
- }
- } else {
- virtualRootMethod.forEach(
- method ->
- virtualRootMethods.put(method.getReference(), rootCandidate.getReference()));
+ method -> setRootMethod(method, virtualRootMethod, singleDispatchTarget));
+ monomorphicVirtualNonRootMethods.add(singleDispatchTarget.getMethod());
+ return;
}
}
+ virtualRootMethod.forEach(
+ method -> setRootMethod(method, virtualRootMethod, virtualRootMethod));
});
}
+ private void setRootMethod(
+ VirtualRootMethod method, VirtualRootMethod currentRoot, VirtualRootMethod root) {
+ // Since the same method can have multiple roots due to interface methods, we only allow
+ // controlling the virtual root of methods that are rooted at the current root. Otherwise, we
+ // would be setting the virtual root of the same method multiple times, which could lead to
+ // non-determinism in the result (i.e., the `virtualRootMethods` map).
+ if (method.getRoot() == currentRoot) {
+ DexMethod rootReference = root.getMethod().getReference();
+ DexMethod previous = virtualRootMethods.put(method.getMethod().getReference(), rootReference);
+ assert previous == null || previous.isIdenticalTo(rootReference);
+ }
+ }
+
protected void acceptVirtualMethod(ProgramMethod method, VirtualRootMethod virtualRootMethod) {
// Intentionally empty.
}
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 675bff8..5fcd8db 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
@@ -108,10 +108,12 @@
return;
}
- // TODO(b/190154391): We should always have an unknown or polymorphic state, but it would
- // be better to use a monomorphic state when the interface method is a default method
- // with no overrides (CF backend only). In this case, there is no need to add methodState
- // to interfaceState.
+ // If the method state is monomorphic, then this is an interface method with no overrides.
+ // In this case, there is no need to add methodState to interfaceState.
+ if (methodState.isMonomorphic()) {
+ return;
+ }
+
assert methodState.isUnknown() || methodState.asConcrete().isPolymorphic();
interfaceState.addMethodState(appView, method, methodState);
});
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 a6e1da7..70b50c9 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
@@ -68,8 +68,8 @@
}
public void run(ExecutorService executorService) throws ExecutionException {
- // TODO(b/335584013): Re-enable monomorphic method analysis.
- ProgramMethodSet monomorphicVirtualMethods = ProgramMethodSet.empty();
+ ProgramMethodSet monomorphicVirtualMethods =
+ computeMonomorphicVirtualRootMethods(executorService);
ProgramMethodMap<ProgramMethod> singleCallerMethods =
new SingleCallerScanner(appView, monomorphicVirtualMethods)
.getSingleCallerMethods(executorService);
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
index 9e78975..2568376 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -57,7 +57,7 @@
return enableCompletenessCheckForTesting
&& !options.isDesugaredLibraryCompilation()
&& !options.getStartupOptions().isStartupCompletenessCheckForTestingEnabled()
- && !options.getStartupInstrumentationOptions().isStartupInstrumentationEnabled();
+ && !options.getInstrumentationOptions().isInstrumentationEnabled();
}
public boolean isNopCheckForTestingEnabled() {
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/InstrumentationOptions.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/InstrumentationOptions.java
new file mode 100644
index 0000000..c1d3fa5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/InstrumentationOptions.java
@@ -0,0 +1,122 @@
+// 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.profile.startup.instrumentation;
+
+import static com.android.tools.r8.utils.SystemPropertyUtils.getSystemPropertyForDevelopment;
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Set;
+
+public class InstrumentationOptions {
+
+ /** Set of method references where all calls to the exact method reference should print. */
+ private Set<DexMethod> callSitesToInstrument = Collections.emptySet();
+
+ /**
+ * When enabled, each method will be instrumented to notify the startup InstrumentationServer that
+ * it has been executed.
+ *
+ * <p>This will also inject the startup runtime library (i.e., the InstrumentationServer) into the
+ * app.
+ */
+ private boolean enableExecutedClassesAndMethodsInstrumentation =
+ parseSystemPropertyForDevelopmentOrDefault(
+ "com.android.tools.r8.instrumentation.executedclassesandmethods", false);
+
+ /**
+ * Specifies the synthetic context of the startup runtime library. When this is set, the startup
+ * runtime library will only be injected into the app when the synthetic context is in the
+ * program. This can be used to avoid that the startup runtime library is injected multiple times
+ * in presence of separate compilation.
+ *
+ * <p>Example synthetic context: "app.tivi.home.MainActivity".
+ *
+ * <p>Note that this is only meaningful when one or more instrumentations are enabled.
+ */
+ private String syntheticServerContext =
+ getSystemPropertyForDevelopment(
+ "com.android.tools.r8.instrumentation.syntheticservercontext");
+
+ /**
+ * Specifies the logcat tag that should be used by the InstrumentationServer when logging events.
+ *
+ * <p>When a logcat tag is not specified, the InstrumentationServer will not print events to
+ * logcat. Instead, the startup events must be obtained by requesting the InstrumentationServer to
+ * write the events to a file.
+ */
+ private String tag = getSystemPropertyForDevelopment("com.android.tools.r8.instrumentation.tag");
+
+ public InstrumentationOptions(InternalOptions options) {
+ String callSitesToInstrumentString =
+ getSystemPropertyForDevelopment("com.android.tools.r8.instrumentation.callsites");
+ if (callSitesToInstrumentString != null) {
+ setCallSitesToInstrument(
+ parseCallSitesToInstrument(callSitesToInstrumentString, options.dexItemFactory()));
+ }
+ }
+
+ private static Set<DexMethod> parseCallSitesToInstrument(
+ String smaliStrings, DexItemFactory factory) {
+ ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+ for (String smaliString : Splitter.on(':').split(smaliStrings)) {
+ MethodReference methodReference = MethodReferenceUtils.parseSmaliString(smaliString);
+ if (methodReference == null) {
+ throw new IllegalArgumentException(smaliString);
+ }
+ builder.add(MethodReferenceUtils.toDexMethod(methodReference, factory));
+ }
+ return builder.build();
+ }
+
+ public boolean isInstrumentationEnabled() {
+ return enableExecutedClassesAndMethodsInstrumentation || !callSitesToInstrument.isEmpty();
+ }
+
+ public Set<DexMethod> getCallSitesToInstrument() {
+ return callSitesToInstrument;
+ }
+
+ public void setCallSitesToInstrument(Set<DexMethod> callSitesToInstrument) {
+ this.callSitesToInstrument = callSitesToInstrument;
+ }
+
+ public boolean isExecutedClassesAndMethodsInstrumentationEnabled() {
+ return enableExecutedClassesAndMethodsInstrumentation;
+ }
+
+ public InstrumentationOptions setEnableExecutedClassesAndMethodsInstrumentation() {
+ enableExecutedClassesAndMethodsInstrumentation = true;
+ return this;
+ }
+
+ public boolean hasSyntheticServerContext() {
+ return syntheticServerContext != null;
+ }
+
+ public String getSyntheticServerContext() {
+ return syntheticServerContext;
+ }
+
+ public boolean hasTag() {
+ return tag != null;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public InstrumentationOptions setTag(String tag) {
+ this.tag = tag;
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
index 521d784..5c43c8b 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
@@ -58,7 +58,7 @@
private final DexItemFactory dexItemFactory;
private final InternalOptions options;
private final StartupInstrumentationReferences references;
- private final StartupInstrumentationOptions startupInstrumentationOptions;
+ private final InstrumentationOptions instrumentationOptions;
private StartupInstrumentation(AppView<AppInfo> appView) {
this.appView = appView;
@@ -66,12 +66,12 @@
this.dexItemFactory = appView.dexItemFactory();
this.options = appView.options();
this.references = new StartupInstrumentationReferences(dexItemFactory);
- this.startupInstrumentationOptions = options.getStartupInstrumentationOptions();
+ this.instrumentationOptions = options.getInstrumentationOptions();
}
public static void run(AppView<AppInfo> appView, ExecutorService executorService)
throws ExecutionException {
- if (appView.options().getStartupInstrumentationOptions().isStartupInstrumentationEnabled()
+ if (appView.options().getInstrumentationOptions().isInstrumentationEnabled()
&& appView.options().isGeneratingDex()) {
StartupInstrumentation startupInstrumentation = new StartupInstrumentation(appView);
startupInstrumentation.instrumentAllClasses(executorService);
@@ -97,11 +97,11 @@
// If the startup options has a synthetic context for the startup instrumentation server, then
// only inject the runtime library if the synthetic context exists in program to avoid injecting
// the runtime library multiple times when there is separate compilation.
- if (startupInstrumentationOptions.hasStartupInstrumentationServerSyntheticContext()) {
+ if (instrumentationOptions.hasSyntheticServerContext()) {
DexType syntheticContext =
dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(
- startupInstrumentationOptions.getStartupInstrumentationServerSyntheticContext()));
+ instrumentationOptions.getSyntheticServerContext()));
if (asProgramClassOrNull(appView.definitionFor(syntheticContext)) == null) {
return;
}
@@ -126,7 +126,7 @@
private List<DexProgramClass> createStartupRuntimeLibraryClasses() {
DexProgramClass instrumentationServerImplClass =
InstrumentationServerImplFactory.createClass(dexItemFactory);
- if (startupInstrumentationOptions.hasStartupInstrumentationTag()) {
+ if (instrumentationOptions.hasTag()) {
instrumentationServerImplClass
.lookupUniqueStaticFieldWithName(dexItemFactory.createString("writeToLogcat"))
.setStaticValue(DexValueBoolean.create(true));
@@ -137,9 +137,7 @@
instrumentationServerImplClass
.lookupUniqueStaticFieldWithName(dexItemFactory.createString("logcatTag"))
.setStaticValue(
- new DexValueString(
- dexItemFactory.createString(
- startupInstrumentationOptions.getStartupInstrumentationTag())));
+ new DexValueString(dexItemFactory.createString(instrumentationOptions.getTag())));
}
return ImmutableList.of(
@@ -162,7 +160,8 @@
}
private boolean ensureClassInitializer(DexProgramClass clazz) {
- if (clazz.hasClassInitializer()) {
+ if (clazz.hasClassInitializer()
+ || !instrumentationOptions.isExecutedClassesAndMethodsInstrumentationEnabled()) {
return false;
}
ComputedApiLevel computedApiLevel =
@@ -191,7 +190,8 @@
instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
// Insert invoke to record that the enclosing class is a startup class.
- if (method.getDefinition().isClassInitializer()) {
+ if (instrumentationOptions.isExecutedClassesAndMethodsInstrumentationEnabled()
+ && method.getDefinition().isClassInitializer()) {
DexType classToPrint = method.getHolderType();
Value descriptorValue =
instructionIterator.insertConstStringInstruction(
@@ -209,15 +209,16 @@
Value descriptorValue =
instructionIterator.insertConstStringInstruction(
appView, code, dexItemFactory.createString(method.getReference().toSmaliString()));
- instructionIterator.add(
- InvokeStatic.builder()
- .setMethod(references.addMethod)
- .setSingleArgument(descriptorValue)
- .setPosition(Position.syntheticNone())
- .build());
+ if (instrumentationOptions.isExecutedClassesAndMethodsInstrumentationEnabled()) {
+ instructionIterator.add(
+ InvokeStatic.builder()
+ .setMethod(references.addMethod)
+ .setSingleArgument(descriptorValue)
+ .setPosition(Position.syntheticNone())
+ .build());
+ }
- Set<DexMethod> callSitesToInstrument =
- startupInstrumentationOptions.getCallSitesToInstrument();
+ Set<DexMethod> callSitesToInstrument = instrumentationOptions.getCallSitesToInstrument();
if (!callSitesToInstrument.isEmpty()) {
do {
while (instructionIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationOptions.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationOptions.java
deleted file mode 100644
index 4a11409..0000000
--- a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentationOptions.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2022, 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.profile.startup.instrumentation;
-
-import static com.android.tools.r8.utils.SystemPropertyUtils.getSystemPropertyForDevelopment;
-import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
-
-import com.android.tools.r8.graph.DexMethod;
-import java.util.Collections;
-import java.util.Set;
-
-public class StartupInstrumentationOptions {
-
- /** Set of method references where all calls to the exact method reference should print. */
- private Set<DexMethod> callSitesToInstrument = Collections.emptySet();
-
- /**
- * When enabled, each method will be instrumented to notify the startup InstrumentationServer that
- * it has been executed.
- *
- * <p>This will also inject the startup runtime library (i.e., the InstrumentationServer) into the
- * app.
- */
- private boolean enableStartupInstrumentation =
- parseSystemPropertyForDevelopmentOrDefault(
- "com.android.tools.r8.startup.instrumentation.instrument", false);
-
- /**
- * Specifies the synthetic context of the startup runtime library. When this is set, the startup
- * runtime library will only be injected into the app when the synthetic context is in the
- * program. This can be used to avoid that the startup runtime library is injected multiple times
- * in presence of separate compilation.
- *
- * <p>Example synthetic context: "app.tivi.home.MainActivity".
- *
- * <p>Note that this is only meaningful when {@link #enableStartupInstrumentation} is set to true.
- */
- private String startupInstrumentationServerSyntheticContext =
- getSystemPropertyForDevelopment(
- "com.android.tools.r8.startup.instrumentation.instrumentationserversyntheticcontext");
-
- /**
- * Specifies the logcat tag that should be used by the InstrumentationServer when logging events.
- *
- * <p>When a logcat tag is not specified, the InstrumentationServer will not print events to
- * logcat. Instead, the startup events must be obtained by requesting the InstrumentationServer to
- * write the events to a file.
- */
- private String startupInstrumentationTag =
- getSystemPropertyForDevelopment(
- "com.android.tools.r8.startup.instrumentation.instrumentationtag");
-
- public Set<DexMethod> getCallSitesToInstrument() {
- return callSitesToInstrument;
- }
-
- public void setCallSitesToInstrument(Set<DexMethod> callSitesToInstrument) {
- this.callSitesToInstrument = callSitesToInstrument;
- }
-
- public boolean hasStartupInstrumentationServerSyntheticContext() {
- return startupInstrumentationServerSyntheticContext != null;
- }
-
- public String getStartupInstrumentationServerSyntheticContext() {
- return startupInstrumentationServerSyntheticContext;
- }
-
- public StartupInstrumentationOptions setStartupInstrumentationServerSyntheticContext(
- String startupInstrumentationServerSyntheticContext) {
- this.startupInstrumentationServerSyntheticContext =
- startupInstrumentationServerSyntheticContext;
- return this;
- }
-
- public boolean hasStartupInstrumentationTag() {
- return startupInstrumentationTag != null;
- }
-
- public String getStartupInstrumentationTag() {
- return startupInstrumentationTag;
- }
-
- public StartupInstrumentationOptions setStartupInstrumentationTag(
- String startupInstrumentationTag) {
- this.startupInstrumentationTag = startupInstrumentationTag;
- return this;
- }
-
- public boolean isStartupInstrumentationEnabled() {
- return enableStartupInstrumentation;
- }
-
- public StartupInstrumentationOptions setEnableStartupInstrumentation() {
- enableStartupInstrumentation = true;
- return this;
- }
-}
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 25a690e..6041570 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -268,6 +268,8 @@
private final Set<EnqueuerConstClassAnalysis> constClassAnalyses = new LinkedHashSet<>();
private final Set<EnqueuerNewInstanceAnalysis> newInstanceAnalyses = new LinkedHashSet<>();
+ private final Map<DexProgramClass, Boolean> rClassLookupCache = new IdentityHashMap<>();
+
// Don't hold a direct pointer to app info (use appView).
private AppInfoWithClassHierarchy appInfo;
private final AppView<AppInfoWithClassHierarchy> appView;
@@ -3804,6 +3806,12 @@
analysis -> analysis.traceInvokeSuper(reference, resolutionResults, context));
}
+ public boolean isRClass(DexProgramClass dexProgramClass) {
+ return rClassLookupCache.computeIfAbsent(
+ dexProgramClass,
+ clazz -> DescriptorUtils.isRClassDescriptor(clazz.getType().toDescriptorString()));
+ }
+
// Returns the set of live types.
public MainDexInfo traceMainDex(ExecutorService executorService, Timing timing)
throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
index 78abb50..51e854f 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -21,13 +21,11 @@
public static EnqueuerDeferredTracing create(
AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer, Mode mode) {
- if (mode.isInitialTreeShaking()) {
+ InternalOptions options = appView.options();
+ if (!options.isShrinking() || !options.enableEnqueuerDeferredTracing) {
return empty();
}
- InternalOptions options = appView.options();
- if (!options.isOptimizing()
- || !options.isShrinking()
- || !options.enableEnqueuerDeferredTracing) {
+ if (!options.isOptimizing() && !options.isOptimizedResourceShrinking()) {
return empty();
}
return new EnqueuerDeferredTracingImpl(appView, enqueuer, mode);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 1eacba7..5291978 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -128,6 +128,11 @@
// field's holder. Therefore, we unconditionally trace the class initializer in this case.
// The corresponding IR rewriter will rewrite the field access into an init-class instruction.
if (accessKind.isStatic()) {
+ if (enqueuer.getMode().isInitialTreeShaking() && field.getHolder() != context.getHolder()) {
+ // TODO(b/338000616): No support for InitClass until we have LIR in the initial round of
+ // tree shaking.
+ return false;
+ }
KeepReason reason =
enqueuer.getGraphReporter().reportClassReferencedFrom(field.getHolder(), context);
enqueuer.getWorklist().enqueueTraceTypeReferenceAction(field.getHolder(), reason);
@@ -148,7 +153,7 @@
}
assert enqueuer.getKeepInfo(field).isBottom();
- assert !enqueuer.getKeepInfo(field).isPinned(options);
+ assert !enqueuer.getKeepInfo(field).isPinned(options) || options.isOptimizedResourceShrinking();
FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
if (info.hasReflectiveAccess()
@@ -162,6 +167,12 @@
|| !minimumKeepInfo.isShrinkingAllowed())) {
return false;
}
+ if (!options.isOptimizing()) {
+ assert options.isOptimizedResourceShrinking();
+ if (!enqueuer.isRClass(field.getHolder())) {
+ return false;
+ }
+ }
if (info.isWritten()) {
// If the assigned value may have an override of Object#finalize() then give up.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index bb72018..4644989 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -684,11 +684,7 @@
// actually equal.
GraphLens graphLens = appView.graphLens();
boolean includeContext =
- intermediate
- || appView
- .options()
- .getStartupInstrumentationOptions()
- .isStartupInstrumentationEnabled();
+ intermediate || appView.options().getInstrumentationOptions().isInstrumentationEnabled();
List<T> sortedPotentialMembers =
ListUtils.sort(
potentialEquivalence,
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 23a0e46..18de8df 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -63,7 +63,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
@@ -89,7 +88,7 @@
import com.android.tools.r8.position.Position;
import com.android.tools.r8.profile.art.ArtProfileOptions;
import com.android.tools.r8.profile.startup.StartupOptions;
-import com.android.tools.r8.profile.startup.instrumentation.StartupInstrumentationOptions;
+import com.android.tools.r8.profile.startup.instrumentation.InstrumentationOptions;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
@@ -198,7 +197,6 @@
public ResourceShrinkerConfiguration resourceShrinkerConfiguration =
ResourceShrinkerConfiguration.DEFAULT_CONFIGURATION;
- public ResourceAccessAnalysis resourceAccessAnalysis = null;
public boolean checkIfCancelled() {
if (cancelCompilationChecker == null) {
@@ -263,6 +261,7 @@
proguardConfiguration = null;
enableTreeShaking = false;
enableMinification = false;
+ instrumentationOptions = new InstrumentationOptions(this);
}
// Constructor for D8, L8, Lint and other non-shrinkers.
@@ -275,6 +274,7 @@
enableTreeShaking = false;
enableMinification = false;
disableGlobalOptimizations();
+ instrumentationOptions = new InstrumentationOptions(this);
}
// Constructor for R8.
@@ -288,6 +288,7 @@
itemFactory = proguardConfiguration.getDexItemFactory();
enableTreeShaking = proguardConfiguration.isShrinking();
enableMinification = proguardConfiguration.isObfuscating();
+ instrumentationOptions = new InstrumentationOptions(this);
if (!proguardConfiguration.isOptimizing()) {
// TODO(b/171457102): Avoid the need for this.
@@ -946,8 +947,7 @@
private final MappingComposeOptions mappingComposeOptions = new MappingComposeOptions();
private final ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
private final StartupOptions startupOptions = new StartupOptions();
- private final StartupInstrumentationOptions startupInstrumentationOptions =
- new StartupInstrumentationOptions();
+ private final InstrumentationOptions instrumentationOptions;
public final TestingOptions testing = new TestingOptions();
public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -1043,8 +1043,8 @@
return startupOptions;
}
- public StartupInstrumentationOptions getStartupInstrumentationOptions() {
- return startupInstrumentationOptions;
+ public InstrumentationOptions getInstrumentationOptions() {
+ return instrumentationOptions;
}
public TestingOptions getTestingOptions() {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
index 377be48..e4872c7 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.ProgramMethod;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -143,6 +144,12 @@
return backing.compute(key, remappingFunction);
}
+ public T compute(
+ ProgramMethod method,
+ BiFunction<? super DexMethodSignature, ? super T, ? extends T> remappingFunction) {
+ return compute(method.getMethodSignature(), remappingFunction);
+ }
+
@Override
public T merge(
DexMethodSignature key,
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
index 07f10ad..90001a5 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -41,38 +40,31 @@
CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
// javac generated an invokedynamic using bootstrap method
// java.lang.runtime.SwitchBootstraps.typeSwitch.
- try {
- assertEquals(
- 1,
- inspector
- .clazz(Main.class)
- .uniqueMethodWithOriginalName("enumSwitch")
- .streamInstructions()
- .filter(InstructionSubject::isInvokeDynamic)
- .map(
- instruction ->
- instruction
- .asCfInstruction()
- .getInstruction()
- .asInvokeDynamic()
- .getCallSite()
- .getBootstrapMethod()
- .member
- .asDexMethod())
- .filter(
- method ->
- method
- .getHolderType()
- .toString()
- .contains("java.lang.runtime.SwitchBootstraps"))
- .filter(method -> method.toString().contains("typeSwitch"))
- .count());
- } catch (CompilationError e) {
- assertEquals("Could not parse code", e.getMessage());
- assertEquals(
- "Unsupported bootstrap static argument of type ConstantDynamic",
- e.getCause().getMessage());
- }
+ assertEquals(
+ 1,
+ inspector
+ .clazz(Main.class)
+ .uniqueMethodWithOriginalName("enumSwitch")
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeDynamic)
+ .map(
+ instruction ->
+ instruction
+ .asCfInstruction()
+ .getInstruction()
+ .asInvokeDynamic()
+ .getCallSite()
+ .getBootstrapMethod()
+ .member
+ .asDexMethod())
+ .filter(
+ method ->
+ method
+ .getHolderType()
+ .toString()
+ .contains("java.lang.runtime.SwitchBootstraps"))
+ .filter(method -> method.toString().contains("typeSwitch"))
+ .count());
parameters.assumeJvmTestParameters();
testForJvm(parameters)
@@ -116,6 +108,7 @@
static final class C implements I {}
static class Main {
+
static void enumSwitch(I i) {
switch (i) {
case E.E1 -> {
diff --git a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
index a330d75..144af9d 100644
--- a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
@@ -104,18 +104,18 @@
case null -> {
System.out.println("null");
}
- case "y", "Y" -> {
- System.out.println("y or Y");
- }
- case "n", "N" -> {
- System.out.println("n or N");
- }
case String s when s.equalsIgnoreCase("YES") -> {
System.out.println("yes");
}
+ case "y", "Y" -> {
+ System.out.println("y or Y");
+ }
case String s when s.equalsIgnoreCase("NO") -> {
System.out.println("no");
}
+ case "n", "N" -> {
+ System.out.println("n or N");
+ }
case String s -> {
System.out.println("unknown");
}
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
index 5ad94ba..f272cde 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -48,24 +47,19 @@
CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
// javac generated an invokedynamic using bootstrap method argument of an arrya type (sort 9
// is org.objectweb.asm.Type.ARRAY).
- try {
inspector
.clazz(Main.class)
.uniqueMethodWithOriginalName("typeSwitch")
.streamInstructions()
.filter(InstructionSubject::isInvokeDynamic)
.count();
- } catch (CompilationError e) {
- assertEquals("Could not parse code", e.getMessage());
- assertEquals("Type sort is not supported: 9", e.getCause().getMessage());
- }
// javac generated an invokedynamic using bootstrap method
// java.lang.runtime.SwitchBootstraps.typeSwitch.
assertEquals(
1,
inspector
.clazz(Main.class)
- .uniqueMethodWithOriginalName("typeSwitchNoArray")
+ .uniqueMethodWithOriginalName("typeSwitch")
.streamInstructions()
.filter(InstructionSubject::isInvokeDynamic)
.map(
@@ -127,17 +121,6 @@
static class Main {
- // No array version only for loading into code inspector.
- static void typeSwitchNoArray(Object obj) {
- switch (obj) {
- case null -> System.out.println("null");
- case String string -> System.out.println("String");
- case Color color -> System.out.println("Color: " + color);
- case Point point -> System.out.println("Record class: " + point);
- default -> System.out.println("Other");
- }
- }
-
static void typeSwitch(Object obj) {
switch (obj) {
case null -> System.out.println("null");
diff --git a/src/test/java/com/android/tools/r8/androidresources/NoOptResourceShrinkingTest.java b/src/test/java/com/android/tools/r8/androidresources/NoOptResourceShrinkingTest.java
new file mode 100644
index 0000000..2248d93
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/NoOptResourceShrinkingTest.java
@@ -0,0 +1,83 @@
+// 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.androidresources;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 NoOptResourceShrinkingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean optimized;
+
+ @Parameters(name = "{0}, optimized: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+ BooleanUtils.values());
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class)
+ .build(temp);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ AndroidTestResource testResources = getTestResources(temp);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(FooBar.class)
+ .applyIf(optimized, R8TestBuilder::enableOptimizedShrinking)
+ .addAndroidResources(testResources)
+ .addKeepMainRule(FooBar.class)
+ .addDontOptimize()
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName("string", "bar");
+ resourceTableInspector.assertContainsResourceWithName("string", "foo");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "unused_string");
+ })
+ .run(parameters.getRuntime(), FooBar.class)
+ .assertSuccess();
+ }
+
+ public static class FooBar {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(R.string.bar);
+ System.out.println(R.string.foo);
+ }
+ }
+ }
+
+ public static class R {
+
+ public static class string {
+ public static int bar;
+ public static int foo;
+ public static int unused_string;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
index 74c7102..584426b 100644
--- a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
+++ b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
@@ -107,7 +107,7 @@
// In optimized mode we track these correctly, so we should not unconditionally keep
// all attributes.
- if (optimized && !debug) {
+ if (optimized) {
resourceTableInspector.assertDoesNotContainResourceWithName(
"attr", "attr_unused_styleable" + i);
} else {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
index c2ab4e2..2cbfd3f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
@@ -38,8 +38,16 @@
.addInnerClasses(EmptyEnumUnboxingTest.class)
.addKeepMainRule(Main.class)
.addKeepRules(enumKeepRules.getKeepRules())
- // TODO(b/166532373): Unbox enum with no cases.
- .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+ .addEnumUnboxingInspector(
+ inspector -> {
+ if (enumKeepRules.isStudio()) {
+ // TODO(b/166532373): Unbox enum with no cases.
+ inspector.assertNotUnboxed(MyEnum.class);
+ } else {
+ assert enumKeepRules.isNone();
+ inspector.assertUnboxed(MyEnum.class);
+ }
+ })
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index ef9401a..bfb7a05 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AssertionUtils;
import com.google.common.collect.Sets;
@@ -24,6 +25,7 @@
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -68,8 +70,10 @@
options -> {
options.testing.processingContextsConsumer =
id -> assertNull(idsRoundOne.put(id, id));
+ options
+ .getOpenClosedInterfacesOptions()
+ .suppressSingleOpenInterface(Reference.classFromClass(Lock.class));
}));
-
compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
Map<String, String> idsRoundTwo = new ConcurrentHashMap<>();
@@ -83,6 +87,9 @@
AssertionUtils.assertNotNull(idsRoundOne.get(id));
assertNull(idsRoundTwo.put(id, id));
};
+ options
+ .getOpenClosedInterfacesOptions()
+ .suppressSingleOpenInterface(Reference.classFromClass(Lock.class));
}));
// Verify that the result of the two compilations was the same.
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index 4c61934..484a349 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -15,9 +15,11 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.concurrent.locks.Lock;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,6 +61,9 @@
builder.addOptionsModification(
options -> {
options.testing.forceJumboStringProcessing = true;
+ options
+ .getOpenClosedInterfacesOptions()
+ .suppressSingleOpenInterface(Reference.classFromClass(Lock.class));
}))
.runDex2Oat(parameters.getRuntime())
.assertNoVerificationErrors();
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
index cd06774..c6cf0e3 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
@@ -91,9 +91,9 @@
.addOptionsModification(
options ->
options
- .getStartupInstrumentationOptions()
- .setEnableStartupInstrumentation()
- .setStartupInstrumentationTag("r8"))
+ .getInstrumentationOptions()
+ .setEnableExecutedClassesAndMethodsInstrumentation()
+ .setTag("r8"))
.enableCoreLibraryDesugaring(
LibraryDesugaringTestConfiguration.builder()
.addDesugaredLibraryConfiguration(
diff --git a/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
index a8cf85a..742d9ce 100644
--- a/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
@@ -78,9 +78,9 @@
.addOptionsModification(
options ->
options
- .getStartupInstrumentationOptions()
- .setEnableStartupInstrumentation()
- .setStartupInstrumentationTag("r8"))
+ .getInstrumentationOptions()
+ .setEnableExecutedClassesAndMethodsInstrumentation()
+ .setTag("r8"))
.setMinApi(apiLevel)
.release()
.compile()
@@ -98,9 +98,9 @@
.addOptionsModification(
options ->
options
- .getStartupInstrumentationOptions()
- .setEnableStartupInstrumentation()
- .setStartupInstrumentationTag("r8"))
+ .getInstrumentationOptions()
+ .setEnableExecutedClassesAndMethodsInstrumentation()
+ .setTag("r8"))
.setMinApi(apiLevel)
.release()
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromBaseMethodWithSingleTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromBaseMethodWithSingleTargetTest.java
new file mode 100644
index 0000000..9a48d47
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromBaseMethodWithSingleTargetTest.java
@@ -0,0 +1,91 @@
+// 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.ir.optimize.callsites;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoParameterTypeStrengthening;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Reproduction of b/336791970 where we incorrectly mark A.m() as a monomorphic method meaning that
+ * the argument "B" in the call that resolves to A.m() is not propagated to B.m().
+ */
+@RunWith(Parameterized.class)
+public class PropagationFromBaseMethodWithSingleTargetTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoParameterTypeStrengtheningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("B", "C");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A a = System.currentTimeMillis() > 0 ? new B() : new A();
+ a.m("B");
+ call(new C());
+ }
+
+ @NeverInline
+ @NoParameterTypeStrengthening
+ static void call(I i) {
+ i.m("C");
+ }
+ }
+
+ @NoVerticalClassMerging
+ interface I {
+
+ void m(String s);
+ }
+
+ static class A {
+
+ public void m(String s) {
+ System.out.println(s);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class B extends A {
+
+ @Override
+ public void m(String s) {
+ System.out.println(s);
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C extends A implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfNonAbstractSiblingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfNonAbstractSiblingTest.java
new file mode 100644
index 0000000..117b090
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfNonAbstractSiblingTest.java
@@ -0,0 +1,78 @@
+// 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.ir.optimize.inliner;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.ImmutableSet;
+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 IllegalSingleCallerInliningOfNonAbstractSiblingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.SINGLE_CALLER))
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "A");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I i = System.currentTimeMillis() > 0 ? new B() : new C();
+ i.m();
+
+ new A().m();
+ }
+ }
+
+ interface I {
+
+ default void m() {
+ System.out.println("C");
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ @NoVerticalClassMerging
+ static class A {
+
+ public void m() {
+ System.out.println("A");
+ }
+ }
+
+ static class B extends A implements I {}
+
+ @NoHorizontalClassMerging
+ static class C implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/PreconditionExtendsRemovedClassIfRuleTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/PreconditionExtendsRemovedClassIfRuleTest.java
new file mode 100644
index 0000000..c709731
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/PreconditionExtendsRemovedClassIfRuleTest.java
@@ -0,0 +1,86 @@
+// 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.shaking.ifrule;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+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 PreconditionExtendsRemovedClassIfRuleTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableUnusedInterfaceRemoval;
+
+ @Parameter(1)
+ public boolean enableVerticalClassMerging;
+
+ @Parameter(2)
+ public TestParameters parameters;
+
+ @Parameters(name = "{2}, unused: {0}, vertical: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-if class * implements " + I.class.getTypeName(),
+ "-keep class <1> {",
+ " void unused();",
+ "}")
+ .applyIf(
+ enableUnusedInterfaceRemoval,
+ R8TestBuilder::addNoUnusedInterfaceRemovalAnnotations,
+ R8TestBuilder::enableNoUnusedInterfaceRemovalAnnotations)
+ .applyIf(
+ enableVerticalClassMerging,
+ R8TestBuilder::addNoVerticalClassMergingAnnotations,
+ R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+ .setMinApi(parameters)
+ .compile()
+ // TODO(b/337905171): Should keep unused.
+ .inspect(
+ inspector ->
+ assertThat(
+ inspector.clazz(A.class).uniqueMethodWithOriginalName("unused"),
+ isAbsentIf(enableUnusedInterfaceRemoval)));
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(A.class);
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ public interface I {}
+
+ public static class A implements I {
+
+ // Kept by -if.
+ void unused() {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/SubsequentExtendsRemovedClassIfRuleTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/SubsequentExtendsRemovedClassIfRuleTest.java
new file mode 100644
index 0000000..0f99416
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/SubsequentExtendsRemovedClassIfRuleTest.java
@@ -0,0 +1,83 @@
+// 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.shaking.ifrule;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+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 SubsequentExtendsRemovedClassIfRuleTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableUnusedInterfaceRemoval;
+
+ @Parameter(1)
+ public boolean enableVerticalClassMerging;
+
+ @Parameter(2)
+ public TestParameters parameters;
+
+ @Parameters(name = "{2}, unused: {0}, vertical: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules("-if interface *", "-keep class * implements <1> {", " void unused();", "}")
+ .applyIf(
+ enableUnusedInterfaceRemoval,
+ R8TestBuilder::addNoUnusedInterfaceRemovalAnnotations,
+ R8TestBuilder::enableNoUnusedInterfaceRemovalAnnotations)
+ .applyIf(
+ enableVerticalClassMerging,
+ R8TestBuilder::addNoVerticalClassMergingAnnotations,
+ R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+ .allowUnusedProguardConfigurationRules(enableUnusedInterfaceRemoval)
+ .setMinApi(parameters)
+ .compile()
+ // TODO(b/337905171): Should keep unused.
+ .inspect(
+ inspector ->
+ assertThat(
+ inspector.clazz(A.class).uniqueMethodWithOriginalName("unused"),
+ isAbsentIf(enableUnusedInterfaceRemoval)));
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(A.class);
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ public interface I {}
+
+ public static class A implements I {
+
+ // Kept by -if.
+ void unused() {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index f51ab4b..41c6730 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -23,7 +23,7 @@
import com.android.tools.r8.profile.art.ArtProfileBuilderUtils;
import com.android.tools.r8.profile.art.HumanReadableArtProfileParser;
import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
-import com.android.tools.r8.profile.startup.instrumentation.StartupInstrumentationOptions;
+import com.android.tools.r8.profile.startup.instrumentation.InstrumentationOptions;
import com.android.tools.r8.startup.StartupClassBuilder;
import com.android.tools.r8.startup.StartupMethodBuilder;
import com.android.tools.r8.startup.StartupProfileBuilder;
@@ -100,11 +100,12 @@
testBuilder
.addOptionsModification(
options -> {
- StartupInstrumentationOptions startupInstrumentationOptions =
- options.getStartupInstrumentationOptions().setEnableStartupInstrumentation();
+ InstrumentationOptions startupInstrumentationOptions =
+ options
+ .getInstrumentationOptions()
+ .setEnableExecutedClassesAndMethodsInstrumentation();
if (logcat) {
- startupInstrumentationOptions.setStartupInstrumentationTag(
- startupInstrumentationTag);
+ startupInstrumentationOptions.setTag(startupInstrumentationTag);
}
})
.addLibraryFiles(parameters.getDefaultRuntimeLibrary())
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 263aa76..6cebc5e 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -17,7 +17,7 @@
import utils
-R8_DEV_BRANCH = '8.5'
+R8_DEV_BRANCH = '8.6'
R8_VERSION_FILE = os.path.join('src', 'main', 'java', 'com', 'android', 'tools',
'r8', 'Version.java')
THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/startup/instrument.py b/tools/startup/instrument.py
index 9752670..7e2870f 100755
--- a/tools/startup/instrument.py
+++ b/tools/startup/instrument.py
@@ -30,6 +30,14 @@
result.add_argument('--discard',
action='append',
help='Name of dex files to discard')
+ result.add_argument('--print-boxing-unboxing-callsites',
+ action='store_true',
+ default=False,
+ help='Print caller->callee edges for primitive boxing')
+ result.add_argument('--print-executed-classes-and-methods',
+ action='store_true',
+ default=False,
+ help='Print the classes and methods that are executed')
result.add_argument('--out',
help='Destination of resulting apk',
required=True)
@@ -52,14 +60,40 @@
tmp_dir):
d8_cmd = [
'java', '-cp', utils.R8_JAR,
- '-Dcom.android.tools.r8.startup.instrumentation.instrument=1',
- '-Dcom.android.tools.r8.startup.instrumentation.instrumentationtag=R8'
+ '-Dcom.android.tools.r8.instrumentation.tag=R8'
]
+ if options.print_boxing_unboxing_callsites:
+ callsites = ':'.join([
+ # Boxing
+ "Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;",
+ "Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte;",
+ "Ljava/lang/Character;->valueOf(C)Ljava/lang/Character;",
+ "Ljava/lang/Double;->valueOf(D)Ljava/lang/Double;",
+ "Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;",
+ "Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;",
+ "Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;",
+ "Ljava/lang/Short;->valueOf(S)Ljava/lang/Short;",
+ # Unboxing
+ "Ljava/lang/Boolean;->booleanValue()Z",
+ "Ljava/lang/Byte;->byteValue()B",
+ "Ljava/lang/Character;->charValue()C",
+ "Ljava/lang/Double;->doubleValue()D",
+ "Ljava/lang/Float;->floatValue()F",
+ "Ljava/lang/Integer;->intValue()I",
+ "Ljava/lang/Long;->longValue()J",
+ "Ljava/lang/Short;->shortValue()S"
+ ])
+ d8_cmd.append(
+ '-Dcom.android.tools.r8.instrumentation.callsites='
+ + callsites)
+ if options.print_executed_classes_and_methods:
+ d8_cmd.append(
+ '-Dcom.android.tools.r8.instrumentation.executedclassesandmethods=1')
if not include_instrumentation_server:
# We avoid injecting the InstrumentationServer by specifying it should only
# be added if foo.bar.Baz is in the program.
d8_cmd.append(
- '-Dcom.android.tools.r8.startup.instrumentation.instrumentationserversyntheticcontext=foo.bar.Baz'
+ '-Dcom.android.tools.r8.instrumentation.syntheticservercontext=foo.bar.Baz'
)
d8_cmd.extend([
'com.android.tools.r8.D8', '--min-api',