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',