Merge commit '0aec7b3cb857285b21e47094b8cf5c56ff6e3b61' into dev-release
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java index 68921ca..cdf0ad1 100644 --- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -230,8 +230,9 @@ promote(Constants.ACC_FINAL); } - public void demoteFromFinal() { + public T demoteFromFinal() { demote(Constants.ACC_FINAL); + return self(); } public boolean isPromotedToPublic() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java index 11d3e17..7689694 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCode.java +++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -8,7 +8,6 @@ import com.android.tools.r8.code.ReturnVoid; import com.android.tools.r8.code.SwitchPayload; import com.android.tools.r8.dex.CodeToKeep; -import com.android.tools.r8.dex.Constants; import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.JumboStringRewriter; import com.android.tools.r8.dex.MixedSectionCollection; @@ -153,7 +152,7 @@ || Arrays.stream(instructions).noneMatch(Instruction::isConstString); assert Arrays.stream(instructions).noneMatch(Instruction::isDexItemBasedConstString); if (highestSortingString != null - && mapping.getOffsetFor(highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) { + && highestSortingString.isGreaterThanOrEqualTo(mapping.getFirstJumboString())) { firstJumboString = mapping.getFirstJumboString(); } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index 865a536..1f58402 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1089,9 +1089,6 @@ public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method, Consumer<Builder> consumer) { checkIfObsolete(); - if (this.getReference() == method) { - return this; - } Builder builder = builder(this); if (isNonPrivateVirtualMethod() && isLibraryMethodOverride() != OptionalBool.unknown()) { builder.setIsLibraryMethodOverride(isLibraryMethodOverride()); @@ -1184,7 +1181,7 @@ } public static DexEncodedMethod createDesugaringForwardingMethod( - DexClassAndMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) { + DexEncodedMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) { DexMethod method = target.getReference(); assert forwardMethod != null; // New method will have the same name, proto, and also all the flags of the @@ -1206,8 +1203,8 @@ .setNonStaticSource(newMethod) .setStaticTarget(forwardMethod, isInterfaceMethodReference) .build()) - .setApiLevelForDefinition(target.getDefinition().getApiLevelForDefinition()) - .setApiLevelForCode(target.getDefinition().getApiLevelForCode()) + .setApiLevelForDefinition(target.getApiLevelForDefinition()) + .setApiLevelForCode(target.getApiLevelForCode()) .build(); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java index f399895..f07c38e 100644 --- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java +++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -64,7 +64,7 @@ } public DexTypeList keepIf(Predicate<DexType> predicate) { - DexType[] filtered = ArrayUtils.filter(DexType[].class, values, predicate); + DexType[] filtered = ArrayUtils.filter(values, predicate, DexType.EMPTY_ARRAY); if (filtered != values) { return DexTypeList.create(filtered); }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java index 6930912..545645e 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java +++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -337,33 +338,75 @@ @Override public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { - replaceDirectMethods(replacement); - replaceVirtualMethods(replacement); + List<DexEncodedMethod> newVirtualMethods = internalReplaceDirectMethods(replacement); + List<DexEncodedMethod> newDirectMethods = internalReplaceVirtualMethods(replacement); + addDirectMethods(newDirectMethods); + addVirtualMethods(newVirtualMethods); } @Override public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + List<DexEncodedMethod> newVirtualMethods = internalReplaceDirectMethods(replacement); + addVirtualMethods(newVirtualMethods); + } + + private List<DexEncodedMethod> internalReplaceDirectMethods( + Function<DexEncodedMethod, DexEncodedMethod> replacement) { + List<DexEncodedMethod> newVirtualMethods = new ArrayList<>(); for (int i = 0; i < directMethods.length; i++) { DexEncodedMethod method = directMethods[i]; DexEncodedMethod newMethod = replacement.apply(method); assert newMethod != null; if (method != newMethod) { - assert belongsToDirectPool(newMethod); - directMethods[i] = newMethod; + if (belongsToDirectPool(newMethod)) { + directMethods[i] = newMethod; + } else { + directMethods[i] = null; + newVirtualMethods.add(newMethod); + } } } + if (!newVirtualMethods.isEmpty()) { + directMethods = + ArrayUtils.filter( + directMethods, + Objects::nonNull, + DexEncodedMethod.EMPTY_ARRAY, + directMethods.length - newVirtualMethods.size()); + } + return newVirtualMethods; } @Override public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + List<DexEncodedMethod> newDirectMethods = internalReplaceVirtualMethods(replacement); + addDirectMethods(newDirectMethods); + } + + private List<DexEncodedMethod> internalReplaceVirtualMethods( + Function<DexEncodedMethod, DexEncodedMethod> replacement) { + List<DexEncodedMethod> newDirectMethods = new ArrayList<>(); for (int i = 0; i < virtualMethods.length; i++) { DexEncodedMethod method = virtualMethods[i]; DexEncodedMethod newMethod = replacement.apply(method); if (method != newMethod) { - assert belongsToVirtualPool(newMethod); - virtualMethods[i] = newMethod; + if (belongsToVirtualPool(newMethod)) { + virtualMethods[i] = newMethod; + } else { + virtualMethods[i] = null; + newDirectMethods.add(newMethod); + } } } + if (!newDirectMethods.isEmpty()) { + virtualMethods = + ArrayUtils.filter( + virtualMethods, + Objects::nonNull, + DexEncodedMethod.EMPTY_ARRAY, + virtualMethods.length - newDirectMethods.size()); + } + return newDirectMethods; } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java index 4f901fd..68c1dde 100644 --- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java +++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -36,6 +36,10 @@ return new Builder(); } + public Builder toBuilder() { + return new Builder(this); + } + public static PrunedItems empty(DexApplication application) { return new Builder().setPrunedApp(application).build(); } @@ -113,6 +117,17 @@ private final Set<DexField> removedFields = Sets.newIdentityHashSet(); private Set<DexMethod> removedMethods = Sets.newIdentityHashSet(); + Builder() {} + + Builder(PrunedItems prunedItems) { + additionalPinnedItems.addAll(prunedItems.getAdditionalPinnedItems()); + noLongerSyntheticItems.addAll(prunedItems.getNoLongerSyntheticItems()); + prunedApp = prunedItems.getPrunedApp(); + removedClasses.addAll(prunedItems.getRemovedClasses()); + removedFields.addAll(prunedItems.getRemovedFields()); + removedMethods.addAll(prunedItems.getRemovedMethods()); + } + public Builder setPrunedApp(DexApplication prunedApp) { this.prunedApp = prunedApp; return this; @@ -129,6 +144,12 @@ return this; } + public Builder addRemovedClass(DexType removedClass) { + this.noLongerSyntheticItems.add(removedClass); + this.removedClasses.add(removedClass); + return this; + } + public Builder addRemovedClasses(Set<DexType> removedClasses) { this.noLongerSyntheticItems.addAll(removedClasses); this.removedClasses.addAll(removedClasses);
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java index dda7746..bf3849c 100644 --- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java +++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.IntObjConsumer; import com.android.tools.r8.utils.IteratorUtils; @@ -450,6 +451,11 @@ return removed; } + public int numberOfRemovedNonReceiverArguments(DexEncodedMethod method) { + return numberOfRemovedArguments() + - BooleanUtils.intValue(method.isInstance() && isArgumentRemoved(0)); + } + public boolean hasArgumentInfo(int argumentIndex) { return argumentInfos.containsKey(argumentIndex); } @@ -542,14 +548,12 @@ } public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) { - // Currently not allowed to remove the receiver of an instance method. This would involve - // changing invoke-direct/invoke-virtual into invoke-static. - assert encodedMethod.isStatic() || !getArgumentInfo(0).isRemovedArgumentInfo(); - DexType[] params = encodedMethod.getReference().proto.parameters.values; + DexType[] params = encodedMethod.getParameters().values; if (isEmpty()) { return params; } - DexType[] newParams = new DexType[params.length - numberOfRemovedArguments()]; + DexType[] newParams = + new DexType[params.length - numberOfRemovedNonReceiverArguments(encodedMethod)]; int offset = encodedMethod.getFirstNonReceiverArgumentIndex(); int newParamIndex = 0; for (int oldParamIndex = 0; oldParamIndex < params.length; oldParamIndex++) {
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java index 97839cf..5d40177 100644 --- a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java +++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
@@ -4,6 +4,11 @@ package com.android.tools.r8.graph.bytecodemetadata; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor; +import java.util.Collections; +import java.util.Set; + /** * A piece of information that can be attached to instructions in {@link * com.android.tools.r8.graph.CfCode} and {@link com.android.tools.r8.graph.DexCode}. @@ -11,6 +16,16 @@ public class BytecodeInstructionMetadata { /** + * Set for field-get instructions that are only used to invoke one or more instance methods. + * + * <p>This is used to skip field-get instructions that will be pruned as a result of method + * staticizing in the {@link TrivialFieldAccessReprocessor}. A common use case for this is Kotlin + * companion fields, which will generally not be read after the methods of a given companion class + * have been staticized. + */ + private final Set<DexMethod> isReadForInvokeReceiver; + + /** * Set for instance and static field read instructions which are only used to write the same * field. * @@ -19,7 +34,9 @@ */ private final boolean isReadForWrite; - BytecodeInstructionMetadata(boolean isReadForWrite) { + private BytecodeInstructionMetadata( + Set<DexMethod> isReadForInvokeReceiver, boolean isReadForWrite) { + this.isReadForInvokeReceiver = isReadForInvokeReceiver; this.isReadForWrite = isReadForWrite; } @@ -31,16 +48,32 @@ return null; } + public boolean isReadForInvokeReceiver() { + return isReadForInvokeReceiver != null; + } + + public Set<DexMethod> getReadForInvokeReceiver() { + return isReadForInvokeReceiver; + } + public boolean isReadForWrite() { return isReadForWrite; } public static class Builder { + private Set<DexMethod> isReadForInvokeReceiver = Collections.emptySet(); private boolean isReadForWrite; private boolean isEmpty() { - return !isReadForWrite; + return isReadForInvokeReceiver.isEmpty() && !isReadForWrite; + } + + public Builder setIsReadForInvokeReceiver(Set<DexMethod> isReadForInvokeReceiver) { + assert isReadForInvokeReceiver != null; + assert !isReadForInvokeReceiver.isEmpty(); + this.isReadForInvokeReceiver = isReadForInvokeReceiver; + return this; } public Builder setIsReadForWrite() { @@ -50,7 +83,7 @@ public BytecodeInstructionMetadata build() { assert !isEmpty(); - return new BytecodeInstructionMetadata(isReadForWrite); + return new BytecodeInstructionMetadata(isReadForInvokeReceiver, isReadForWrite); } } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java index c76f43f..d7dc429 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -23,6 +23,7 @@ import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ProgramMember; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode; import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger; import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter; @@ -290,16 +291,21 @@ group.getTarget().setInterfaces(DexTypeList.create(interfaces)); } - void mergeFields() { + void mergeFields(PrunedItems.Builder prunedItemsBuilder) { if (group.hasClassIdField()) { appendClassIdField(); } - mergeInstanceFields(); + mergeInstanceFields(prunedItemsBuilder); mergeStaticFields(); } - void mergeInstanceFields() { - group.forEachSource(DexClass::clearInstanceFields); + void mergeInstanceFields(PrunedItems.Builder prunedItemsBuilder) { + group.forEachSource( + clazz -> { + clazz.forEachInstanceField( + field -> prunedItemsBuilder.addRemovedField(field.getReference())); + clazz.clearInstanceFields(); + }); group.getTarget().setInstanceFields(classInstanceFieldsMerger.merge()); } @@ -310,16 +316,18 @@ } public void mergeGroup( + PrunedItems.Builder prunedItemsBuilder, SyntheticArgumentClass syntheticArgumentClass, SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) { fixAccessFlags(); fixNestMemberAttributes(); mergeAnnotations(); mergeInterfaces(); - mergeFields(); + mergeFields(prunedItemsBuilder); mergeMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder); group.getTarget().clearClassSignature(); group.getTarget().forEachProgramMember(ProgramMember::clearGenericSignature); + group.forEachSource(clazz -> prunedItemsBuilder.addRemovedClass(clazz.getType())); } public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index b14f07c..ad8d9df 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -103,7 +103,9 @@ : null; SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder = SyntheticInitializerConverter.builder(appView, codeProvider); - applyClassMergers(classMergers, syntheticArgumentClass, syntheticInitializerConverterBuilder); + PrunedItems prunedItems = + applyClassMergers( + classMergers, syntheticArgumentClass, syntheticInitializerConverterBuilder); SyntheticInitializerConverter syntheticInitializerConverter = syntheticInitializerConverterBuilder.build(); @@ -124,10 +126,7 @@ // Prune keep info. KeepInfoCollection keepInfo = appView.getKeepInfo(); - keepInfo.mutate( - mutator -> - mutator.removeKeepInfoForMergedClasses( - PrunedItems.builder().setRemovedClasses(mergedClasses.getSources()).build())); + keepInfo.mutate(mutator -> mutator.removeKeepInfoForMergedClasses(prunedItems)); // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation // sites, fields accesses, etc. are correctly transferred to the target classes. @@ -142,12 +141,7 @@ } appView.pruneItems( - PrunedItems.builder() - .setPrunedApp(appView.appInfo().app()) - .addRemovedClasses(mergedClasses.getSources()) - .addNoLongerSyntheticItems(mergedClasses.getSources()) - .build(), - executorService); + prunedItems.toBuilder().setPrunedApp(appView.app()).build(), executorService); } private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier( @@ -220,13 +214,16 @@ } /** Merges all class groups using {@link ClassMerger}. */ - private void applyClassMergers( + private PrunedItems applyClassMergers( Collection<ClassMerger> classMergers, SyntheticArgumentClass syntheticArgumentClass, SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) { + PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app()); for (ClassMerger merger : classMergers) { - merger.mergeGroup(syntheticArgumentClass, syntheticInitializerConverterBuilder); + merger.mergeGroup( + prunedItemsBuilder, syntheticArgumentClass, syntheticInitializerConverterBuilder); } + return prunedItemsBuilder.build(); } /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java index e5a1aec..3ebd37a 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -28,6 +28,7 @@ private final AppView<? extends AppInfoWithClassHierarchy> appView; private final FieldAssignmentTracker fieldAssignmentTracker; private final FieldBitAccessAnalysis fieldBitAccessAnalysis; + private final FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis; private final FieldReadForWriteAnalysis fieldReadForWriteAnalysis; public FieldAccessAnalysis(AppView<AppInfoWithLiveness> appView) { @@ -36,6 +37,7 @@ this.fieldBitAccessAnalysis = options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null; this.fieldAssignmentTracker = new FieldAssignmentTracker(appView); + this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView); this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); } @@ -44,10 +46,12 @@ AppView<? extends AppInfoWithClassHierarchy> appView, FieldAssignmentTracker fieldAssignmentTracker, FieldBitAccessAnalysis fieldBitAccessAnalysis, + FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis, FieldReadForWriteAnalysis fieldReadForWriteAnalysis) { this.appView = appView; this.fieldAssignmentTracker = fieldAssignmentTracker; this.fieldBitAccessAnalysis = fieldBitAccessAnalysis; + this.fieldReadForInvokeReceiverAnalysis = fieldReadForInvokeReceiverAnalysis; this.fieldReadForWriteAnalysis = fieldReadForWriteAnalysis; } @@ -91,6 +95,10 @@ fieldBitAccessAnalysis.recordFieldAccess( fieldInstruction, field.getDefinition(), feedback); } + if (fieldReadForInvokeReceiverAnalysis != null) { + fieldReadForInvokeReceiverAnalysis.recordFieldAccess( + fieldInstruction, field, bytecodeMetadataProviderBuilder, code.context()); + } if (fieldReadForWriteAnalysis != null) { fieldReadForWriteAnalysis.recordFieldAccess( fieldInstruction, field, bytecodeMetadataProviderBuilder);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java new file mode 100644 index 0000000..b2aded5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
@@ -0,0 +1,90 @@ +// 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.ir.analysis.fieldaccess; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.ProgramField; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider; +import com.android.tools.r8.ir.code.FieldInstruction; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; +import com.android.tools.r8.ir.code.StaticGet; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.WorkList; +import com.google.common.collect.Sets; +import java.util.Set; + +public class FieldReadForInvokeReceiverAnalysis { + + private final AppView<AppInfoWithLiveness> appView; + + FieldReadForInvokeReceiverAnalysis(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + } + + public void recordFieldAccess( + FieldInstruction instruction, + ProgramField field, + BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder, + ProgramMethod context) { + if (!instruction.isStaticGet()) { + return; + } + + StaticGet staticGet = instruction.asStaticGet(); + Set<DexMethod> methods = getMethods(staticGet.outValue(), context); + if (methods == null || methods.isEmpty()) { + return; + } + + bytecodeMetadataProviderBuilder.addMetadata( + instruction, builder -> builder.setIsReadForInvokeReceiver(methods)); + } + + private Set<DexMethod> getMethods(Value value, ProgramMethod context) { + WorkList<Instruction> users = WorkList.newIdentityWorkList(); + if (!enqueueUsersForAnalysis(value, users)) { + return null; + } + Set<DexMethod> methods = Sets.newIdentityHashSet(); + while (users.hasNext()) { + Instruction user = users.next(); + if (user.isAssume()) { + if (enqueueUsersForAnalysis(user.outValue(), users)) { + // OK. + continue; + } + } else if (user.isInvokeMethodWithReceiver()) { + InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver(); + for (int argumentIndex = 1; argumentIndex < invoke.arguments().size(); argumentIndex++) { + if (invoke.getArgument(argumentIndex).getAliasedValue() == value) { + return null; + } + } + assert invoke.getReceiver().getAliasedValue() == value; + + ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context); + if (singleTarget == null) { + return null; + } + + methods.add(singleTarget.getReference()); + } + return null; + } + return methods; + } + + private boolean enqueueUsersForAnalysis(Value value, WorkList<Instruction> users) { + if (value.hasDebugUsers() || value.hasPhiUsers()) { + return false; + } + users.addIfNotSeen(value.uniqueUsers()); + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java index f392dec..7f36c0c 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -4,7 +4,9 @@ package com.android.tools.r8.ir.analysis.fieldaccess; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback; +import static com.android.tools.r8.utils.MapUtils.ignoreKey; import com.android.tools.r8.code.CfOrDexInstanceFieldRead; import com.android.tools.r8.code.CfOrDexStaticFieldRead; @@ -53,6 +55,8 @@ private final AppView<AppInfoWithLiveness> appView; private final PostMethodProcessor.Builder postMethodProcessorBuilder; + private final Map<DexEncodedField, ProgramMethodSet> dependencies = new ConcurrentHashMap<>(); + /** Updated concurrently from {@link #processClass(DexProgramClass)}. */ private final Map<DexEncodedField, AbstractAccessContexts> readFields = new ConcurrentHashMap<>(); @@ -233,6 +237,7 @@ fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads(); methodsToReprocess.addAll( contexts.asConcrete().getAccessesWithContexts().values().iterator().next()); + methodsToReprocess.addAll(dependencies.getOrDefault(field, ProgramMethodSet.empty())); }); } @@ -250,6 +255,7 @@ assert !writtenFields.containsKey(field); methodsToReprocess.addAll( contexts.asConcrete().getAccessesWithContexts().values().iterator().next()); + methodsToReprocess.addAll(dependencies.getOrDefault(field, ProgramMethodSet.empty())); }); } @@ -326,11 +332,20 @@ return; } - if (metadata != null && metadata.isReadForWrite()) { - // Ignore this read. If the field ends up only being written, then we will still reprocess - // the method with the read-for-write instruction, since the method contains a write that - // requires reprocessing. - return; + if (metadata != null) { + if (isUnusedReadAfterMethodStaticizing(metadata)) { + // Ignore this read. + dependencies + .computeIfAbsent(field.getDefinition(), ignoreKey(ProgramMethodSet::createConcurrent)) + .add(getContext()); + return; + } + if (metadata.isReadForWrite()) { + // Ignore this read. If the field ends up only being written, then we will still reprocess + // the method with the read-for-write instruction, since the method contains a write that + // requires reprocessing. + return; + } } // Record access. @@ -352,6 +367,28 @@ } } + private boolean isUnusedReadAfterMethodStaticizing(BytecodeInstructionMetadata metadata) { + if (!metadata.isReadForInvokeReceiver()) { + return false; + } + Set<DexMethod> readForInvokeReceiver = metadata.getReadForInvokeReceiver(); + for (DexMethod methodReference : readForInvokeReceiver) { + DexMethod rewrittenMethodReference = + appView.graphLens().getRenamedMethodSignature(methodReference, appView.codeLens()); + DexProgramClass holder = + asProgramClassOrNull(appView.definitionFor(rewrittenMethodReference.getHolderType())); + ProgramMethod method = rewrittenMethodReference.lookupOnProgramClass(holder); + if (method == null) { + assert false; + return false; + } + if (!method.getDefinition().isStatic()) { + return false; + } + } + return true; + } + private void recordAccessThatCannotBeOptimized( DexClassAndField field, DexEncodedField definition) { constantFields.remove(definition);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java index 66747a7..d0cfd52 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -295,9 +295,9 @@ return; } - assert definition.isInvokeMethodWithReceiver() + assert definition.isInvokeMethod() && references.isFindLiteExtensionByNumberMethod( - definition.asInvokeMethodWithReceiver().getInvokedMethod()); + definition.asInvokeMethod().getInvokedMethod()); } /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java index 62cbb04..2ad1f65 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -275,6 +275,40 @@ } @Override + public InvokeMethod insertNullCheckInstruction( + AppView<?> appView, + IRCode code, + BasicBlockIterator blockIterator, + Value value, + Position position) { + InternalOptions options = appView.options(); + + InvokeMethod invoke; + if (appView.options().canUseJavaUtilObjectsRequireNonNull()) { + DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull; + invoke = + InvokeStatic.builder() + .setMethod(requireNonNullMethod) + .setSingleArgument(value) + .setPosition(position) + .build(); + } else { + DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass; + invoke = + InvokeVirtual.builder() + .setMethod(getClassMethod) + .setSingleArgument(value) + .setPosition(position) + .build(); + } + add(invoke); + if (block.hasCatchHandlers()) { + splitCopyCatchHandlers(code, blockIterator, options); + } + return invoke; + } + + @Override public boolean replaceCurrentInstructionByNullCheckIfPossible( AppView<?> appView, ProgramMethod context) { Instruction toBeReplaced = current;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java index f9bbcc8..a68da1a 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -45,6 +45,17 @@ } @Override + public InvokeMethod insertNullCheckInstruction( + AppView<?> appView, + IRCode code, + BasicBlockIterator blockIterator, + Value value, + Position position) { + return instructionIterator.insertNullCheckInstruction( + appView, code, blockIterator, value, position); + } + + @Override public boolean replaceCurrentInstructionByNullCheckIfPossible( AppView<?> appView, ProgramMethod context) { return instructionIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java index d08413d..bc6bec6 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -100,6 +100,13 @@ Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value); + InvokeMethod insertNullCheckInstruction( + AppView<?> appView, + IRCode code, + BasicBlockIterator blockIterator, + Value value, + Position position); + boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context); boolean replaceCurrentInstructionByInitClassIfPossible(
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java index cb20fd4..37d45df 100644 --- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -69,6 +69,17 @@ } @Override + public InvokeMethod insertNullCheckInstruction( + AppView<?> appView, + IRCode code, + BasicBlockIterator blockIterator, + Value value, + Position position) { + return currentBlockIterator.insertNullCheckInstruction( + appView, code, blockIterator, value, position); + } + + @Override public boolean replaceCurrentInstructionByNullCheckIfPossible( AppView<?> appView, ProgramMethod context) { return currentBlockIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java index 47549e9..9fd0c74 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Position.java +++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -13,6 +13,7 @@ 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 java.util.function.Predicate; public abstract class Position implements StructuralItem<Position> { @@ -150,6 +151,26 @@ return lastPosition; } + public Position getOutermostCallerMatchingOrElse( + Predicate<Position> predicate, Position defaultValue) { + return getOutermostCallerMatchingOrElse(predicate, defaultValue, false); + } + + private Position getOutermostCallerMatchingOrElse( + Predicate<Position> predicate, Position defaultValue, boolean isCallerPosition) { + if (hasCallerPosition()) { + Position position = + getCallerPosition().getOutermostCallerMatchingOrElse(predicate, defaultValue, true); + if (position != null) { + return position; + } + } + if (isCallerPosition && predicate.test(this)) { + return this; + } + return defaultValue; + } + public Position withOutermostCallerPosition(Position newOutermostCallerPosition) { return builderWithCopy() .setCallerPosition(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index c4dba5e..45758d3 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1505,6 +1505,9 @@ CodeRewriter.removeAssumeInstructions(appView, code); timing.end(); assert code.isConsistentSSA(); + + // TODO(b/214496607): Remove when dynamic types are safe w.r.t. interface assignment rules. + codeRewriter.rewriteMoveResult(code); } // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index f6c957c..e5602b2 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -100,7 +100,6 @@ import com.android.tools.r8.optimize.MemberRebindingAnalysis; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper; -import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.HashSet; import java.util.IdentityHashMap; @@ -151,10 +150,7 @@ /** Replace type appearances, invoke targets and field accesses with actual definitions. */ public void rewrite(IRCode code, ProgramMethod method, MethodProcessor methodProcessor) { - Set<Phi> affectedPhis = - enumUnboxer != null - ? enumUnboxer.rewriteCode(code, methodProcessor) - : Sets.newIdentityHashSet(); + Set<Phi> affectedPhis = enumUnboxer.rewriteCode(code, methodProcessor); GraphLens graphLens = appView.graphLens(); DexItemFactory factory = appView.dexItemFactory(); // Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this @@ -256,6 +252,9 @@ Invoke.Type actualInvokeType = lensLookup.getType(); iterator = + insertNullCheckForInvokeReceiverIfNeeded( + code, blocks, iterator, invoke, lensLookup); + iterator = insertCastsForInvokeArgumentsIfNeeded(code, blocks, iterator, invoke, lensLookup); RewrittenPrototypeDescription prototypeChanges = lensLookup.getPrototypeChanges(); @@ -772,6 +771,56 @@ return iterator; } + private InstructionListIterator insertNullCheckForInvokeReceiverIfNeeded( + IRCode code, + BasicBlockIterator blocks, + InstructionListIterator iterator, + InvokeMethod invoke, + MethodLookupResult lookup) { + // If the invoke has been staticized, then synthesize a null check for the receiver. + if (!invoke.isInvokeMethodWithReceiver() || !lookup.getType().isStatic()) { + return iterator; + } + + TypeElement receiverType = invoke.asInvokeMethodWithReceiver().getReceiver().getType(); + if (receiverType.isDefinitelyNotNull()) { + return iterator; + } + + iterator.previous(); + + Position nullCheckPosition = + invoke + .getPosition() + .getOutermostCallerMatchingOrElse( + Position::isRemoveInnerFramesIfThrowingNpe, invoke.getPosition()); + InvokeMethod nullCheck = + iterator.insertNullCheckInstruction( + appView, code, blocks, invoke.getFirstArgument(), nullCheckPosition); + + // TODO(b/199864962): Lens code rewriting should follow the order of graph lenses, i.e., enum + // unboxing rewriting should happen after method staticizing. + if (receiverType.isClassType() + && appView.unboxedEnums().isUnboxedEnum(receiverType.asClassType().getClassType())) { + iterator.previousUntil(instruction -> instruction == nullCheck); + iterator.next(); + enumUnboxer.rewriteNullCheck(iterator, nullCheck); + } + + // Reset the block iterator. + if (invoke.getBlock().hasCatchHandlers()) { + BasicBlock splitBlock = invoke.getBlock(); + BasicBlock previousBlock = blocks.previousUntil(block -> block == splitBlock); + assert previousBlock == splitBlock; + blocks.next(); + iterator = splitBlock.listIterator(code); + } + + Instruction next = iterator.next(); + assert next == invoke; + return iterator; + } + private InstructionListIterator insertCastsForInvokeArgumentsIfNeeded( IRCode code, BasicBlockIterator blocks,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java index a3fc0e8..ee7f8e1 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1091,27 +1091,44 @@ } private void initializeAndroidSv2MethodProviders(DexItemFactory factory) { - DexString name; - DexProto proto; - DexMethod method; // sun.misc.Unsafe + { + // compareAndSwapObject(Object receiver, long offset, Object expect, Object update) + DexType type = factory.unsafeType; + DexString name = factory.createString("compareAndSwapObject"); + DexProto proto = + factory.createProto( + factory.booleanType, + factory.objectType, + factory.longType, + factory.objectType, + factory.objectType); + DexMethod method = factory.createMethod(type, proto, name); + addProvider( + new StatifyingMethodWithForwardingGenerator( + method, + BackportedMethods::UnsafeMethods_compareAndSwapObject, + "compareAndSwapObject", + type)); + } - // compareAndSwapObject(Object receiver, long offset, Object expect, Object update) - name = factory.createString("compareAndSwapObject"); - proto = - factory.createProto( - factory.booleanType, - factory.objectType, - factory.longType, - factory.objectType, - factory.objectType); - method = factory.createMethod(factory.unsafeType, proto, name); - addProvider( - new StatifyingMethodWithForwardingGenerator( - method, - BackportedMethods::UnsafeMethods_compareAndSwapObject, - "compareAndSwapObject", - factory.unsafeType)); + // java.util.concurrent.atomic.AtomicReferenceFieldUpdater + { + // compareAndSet(Object object, Object expect, Object update) + DexType type = + factory.createType("Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"); + DexString name = factory.createString("compareAndSet"); + DexProto proto = + factory.createProto( + factory.booleanType, factory.objectType, factory.objectType, factory.objectType); + DexMethod method = factory.createMethod(type, proto, name); + addProvider( + new StatifyingMethodWithForwardingGenerator( + method, + BackportedMethods::AtomicReferenceFieldUpdaterMethods_compareAndSet, + "compareAndSet", + type)); + } } private void initializeJava9MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java index b934c40..e6b793f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -97,6 +97,7 @@ factory.createSynthesizedType("Ljava/util/OptionalInt;"); factory.createSynthesizedType("Ljava/util/OptionalLong;"); factory.createSynthesizedType("Ljava/util/Set;"); + factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"); factory.createSynthesizedType("Ljava/util/function/Consumer;"); factory.createSynthesizedType("Ljava/util/function/DoubleConsumer;"); factory.createSynthesizedType("Ljava/util/function/IntConsumer;"); @@ -114,6 +115,85 @@ factory.createSynthesizedType("[Ljava/util/Map$Entry;"); } + public static CfCode AtomicReferenceFieldUpdaterMethods_compareAndSet( + InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 4, + ImmutableList.of( + label0, + new CfFrame( + new Int2ReferenceAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initialized( + options.itemFactory.createType( + "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")), + FrameType.initialized(options.itemFactory.objectType), + FrameType.initialized(options.itemFactory.objectType), + FrameType.initialized(options.itemFactory.objectType) + }), + new ArrayDeque<>(Arrays.asList())), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 2), + new CfLoad(ValueType.OBJECT, 3), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType( + "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"), + options.itemFactory.createProto( + options.itemFactory.booleanType, + options.itemFactory.objectType, + options.itemFactory.objectType, + options.itemFactory.objectType), + options.itemFactory.createString("compareAndSet")), + false), + new CfIf(If.Type.EQ, ValueType.INT, label2), + label1, + new CfConstNumber(1, ValueType.INT), + new CfReturn(ValueType.INT), + label2, + new CfFrame( + new Int2ReferenceAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initialized( + options.itemFactory.createType( + "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")), + FrameType.initialized(options.itemFactory.objectType), + FrameType.initialized(options.itemFactory.objectType), + FrameType.initialized(options.itemFactory.objectType) + }), + new ArrayDeque<>(Arrays.asList())), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType( + "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"), + options.itemFactory.createProto( + options.itemFactory.objectType, options.itemFactory.objectType), + options.itemFactory.createString("get")), + false), + new CfLoad(ValueType.OBJECT, 2), + new CfIfCmp(If.Type.EQ, ValueType.OBJECT, label0), + label3, + new CfConstNumber(0, ValueType.INT), + new CfReturn(ValueType.INT), + label4), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode BooleanMethods_compare(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java new file mode 100644 index 0000000..c383dcd --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
@@ -0,0 +1,52 @@ +// 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.ir.desugar.desugaredlibrary.machinespecification; + +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProto; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; + +/** + * A derived method is: - if the holderKind is null, a normal dexMethod; - if the holderKind is + * non-null, a method derived from the dexMethod In that case the method holder is used as the + * context to generate the holder type. The method may however differ (for example the method name + * may be different). + */ +public class DerivedMethod { + + private final DexMethod method; + private final SyntheticKind holderKind; + + public DerivedMethod(DexMethod method) { + this(method, null); + } + + public DerivedMethod(DexMethod method, SyntheticKind holderKind) { + this.holderKind = holderKind; + this.method = method; + } + + public SyntheticKind getHolderKind() { + return holderKind; + } + + public DexType getHolderContext() { + return method.getHolderType(); + } + + public DexMethod getMethod() { + return method; + } + + public DexString getName() { + return method.getName(); + } + + public DexProto getProto() { + return method.getProto(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java new file mode 100644 index 0000000..c5154cd --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
@@ -0,0 +1,71 @@ +// 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.ir.desugar.desugaredlibrary.machinespecification; + +import com.android.tools.r8.graph.DexType; +import java.util.LinkedHashMap; + +public class EmulatedDispatchMethodDescriptor { + + /** + * When resolving into the descriptor, if the resolution is used for a super-invoke or to generate + * a forwarding method, then the forwarding method should be used. If the resolution is used to + * rewrite an invoke, then it should be rewritten to an invoke-static to the emulated dispatch + * method. + * + * <p>Emulated dispatch method are generated as follows: <code>emulatedDispatchMethod { + * if (rcvr instanceof itf) { + * invoke-interface itfMethod + * } + * if (rcvr instanceof dispatchCases0) { + * invoke-static dispatchCases0 + * } + * ... + * if (rcvr instanceof dispatchCasesN) { + * invoke-static dispatchCasesN + * } + * invoke-static forwardingMethod }</code> + * + * <p>For emulatedVirtualRetarget instances, the itfMethod holder and emulatedDispatchMethod + * holder are the itf and emulated dispatch holder types to synthesize. The forwardingMethod is + * the method to retarget to. + * + * <p>For emulated interface, itfMethod holder is the rewritten emulated interface type and the + * emulated dispatch method is on the $-EI class holding the dispatch methods. The forwarding + * method is the method on the companion class. + */ + private final DerivedMethod interfaceMethod; + + private final DerivedMethod emulatedDispatchMethod; + private final DerivedMethod forwardingMethod; + private final LinkedHashMap<DexType, DerivedMethod> dispatchCases; + + public EmulatedDispatchMethodDescriptor( + DerivedMethod interfaceMethod, + DerivedMethod emulatedDispatchMethod, + DerivedMethod forwardingMethod, + LinkedHashMap<DexType, DerivedMethod> dispatchCases) { + this.interfaceMethod = interfaceMethod; + this.emulatedDispatchMethod = emulatedDispatchMethod; + this.forwardingMethod = forwardingMethod; + this.dispatchCases = dispatchCases; + } + + public DerivedMethod getInterfaceMethod() { + return interfaceMethod; + } + + public DerivedMethod getEmulatedDispatchMethod() { + return emulatedDispatchMethod; + } + + public DerivedMethod getForwardingMethod() { + return forwardingMethod; + } + + public LinkedHashMap<DexType, DerivedMethod> getDispatchCases() { + return dispatchCases; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java new file mode 100644 index 0000000..2a88f9a --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -0,0 +1,26 @@ +// 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.ir.desugar.desugaredlibrary.machinespecification; + +/** TODO(b/184026720): Move the rest of the flags. */ +public class MachineDesugaredLibrarySpecification { + + private final boolean libraryCompilation; + private final MachineRewritingFlags rewritingFlags; + + public MachineDesugaredLibrarySpecification( + boolean libraryCompilation, MachineRewritingFlags rewritingFlags) { + this.libraryCompilation = libraryCompilation; + this.rewritingFlags = rewritingFlags; + } + + public boolean isLibraryCompilation() { + return libraryCompilation; + } + + public MachineRewritingFlags getRewritingFlags() { + return rewritingFlags; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java new file mode 100644 index 0000000..f73e1c9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -0,0 +1,82 @@ +// 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.ir.desugar.desugaredlibrary.machinespecification; + +import com.android.tools.r8.graph.DexMethod; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +public class MachineRewritingFlags { + + public static Builder builder() { + return new Builder(); + } + + MachineRewritingFlags( + Map<DexMethod, DexMethod> staticRetarget, + Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget, + Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) { + this.staticRetarget = staticRetarget; + this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget; + this.emulatedVirtualRetarget = emulatedVirtualRetarget; + } + + // Static methods to retarget, duplicated to library boundaries. + private final Map<DexMethod, DexMethod> staticRetarget; + + // Virtual methods to retarget, which are guaranteed not to require emulated dispatch. + // A method does not require emulated dispatch if two conditions are met: + // (1) the method does not override any other library method; + // (2) the method is final or installed in a final class. + // Any invoke resolving into the method will be rewritten into an invoke-static to the desugared + // code. + private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget; + + // Virtual methods to retarget through emulated dispatch. + private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget; + + public Map<DexMethod, DexMethod> getStaticRetarget() { + return staticRetarget; + } + + public Map<DexMethod, DexMethod> getNonEmulatedVirtualRetarget() { + return nonEmulatedVirtualRetarget; + } + + public Map<DexMethod, EmulatedDispatchMethodDescriptor> getEmulatedVirtualRetarget() { + return emulatedVirtualRetarget; + } + + public static class Builder { + + Builder() {} + + private final ImmutableMap.Builder<DexMethod, DexMethod> staticRetarget = + ImmutableMap.builder(); + private final ImmutableMap.Builder<DexMethod, DexMethod> nonEmulatedVirtualRetarget = + ImmutableMap.builder(); + private final ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor> + emulatedVirtualRetarget = ImmutableMap.builder(); + + public void putStaticRetarget(DexMethod src, DexMethod dest) { + staticRetarget.put(src, dest); + } + + public void putNonEmulatedVirtualRetarget(DexMethod src, DexMethod dest) { + nonEmulatedVirtualRetarget.put(src, dest); + } + + public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) { + emulatedVirtualRetarget.put(src, dest); + } + + public MachineRewritingFlags build() { + return new MachineRewritingFlags( + staticRetarget.build(), + nonEmulatedVirtualRetarget.build(), + emulatedVirtualRetarget.build()); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java index c4a01f6..7c5d1c3 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
@@ -9,14 +9,12 @@ import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProto; -import com.android.tools.r8.graph.DexString; -import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.MethodResolutionResult; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.desugar.CfInstructionDesugaring; @@ -24,11 +22,10 @@ import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.FreshLocalProvider; import com.android.tools.r8.ir.desugar.LocalStackAllocator; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor; import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer; -import com.android.tools.r8.utils.collections.DexClassAndMethodSet; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; @@ -40,22 +37,24 @@ private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper; private final RetargetingInfo retargetingInfo; - private final Map<DexMethod, DexMethod> retargetLibraryMember; - private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites; - private final DexClassAndMethodSet emulatedDispatchMethods; + private final Map<DexMethod, DexMethod> staticRetarget; + private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget; + private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget; public DesugaredLibraryRetargeter(AppView<?> appView) { this.appView = appView; this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView); retargetingInfo = RetargetingInfo.get(appView); - retargetLibraryMember = retargetingInfo.getRetargetLibraryMember(); - nonFinalHolderRewrites = retargetingInfo.getNonFinalHolderRewrites(); - emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods(); + staticRetarget = retargetingInfo.getStaticRetarget(); + nonEmulatedVirtualRetarget = retargetingInfo.getNonEmulatedVirtualRetarget(); + emulatedVirtualRetarget = retargetingInfo.getEmulatedVirtualRetarget(); } // Used by the ListOfBackportedMethods utility. public void visit(Consumer<DexMethod> consumer) { - retargetLibraryMember.keySet().forEach(consumer); + staticRetarget.keySet().forEach(consumer); + nonEmulatedVirtualRetarget.keySet().forEach(consumer); + emulatedVirtualRetarget.keySet().forEach(consumer); } public RetargetingInfo getRetargetingInfo() { @@ -125,7 +124,7 @@ private InvokeRetargetingResult computeNewInvokeTarget( CfInstruction instruction, ProgramMethod context) { - if (retargetLibraryMember.isEmpty() || !instruction.isInvoke()) { + if (!instruction.isInvoke()) { return NO_REWRITING; } if (appView @@ -137,77 +136,56 @@ } CfInvoke cfInvoke = instruction.asInvoke(); DexMethod invokedMethod = cfInvoke.getMethod(); - InvokeRetargetingResult retarget = - computeRetargetedMethod(invokedMethod, cfInvoke.isInterface()); + AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring(); + MethodResolutionResult resolutionResult = + appInfo.resolveMethod(invokedMethod, cfInvoke.isInterface()); + if (!resolutionResult.isSingleResolution()) { + return NO_REWRITING; + } + DexEncodedMethod singleTarget = resolutionResult.getSingleTarget(); + assert singleTarget != null; + if (cfInvoke.isInvokeStatic()) { + DexMethod retarget = staticRetarget.get(singleTarget.getReference()); + return retarget == null + ? NO_REWRITING + : InvokeRetargetingResult.createInvokeRetargetingResult(retarget); + } + InvokeRetargetingResult retarget = computeNonStaticRetarget(singleTarget); if (!retarget.hasNewInvokeTarget()) { return NO_REWRITING; } - if (cfInvoke.isInvokeSuper(context.getHolderType()) - && matchesNonFinalHolderRewrite(invokedMethod)) { - DexClassAndMethod superTarget = - appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context); - // Final methods can be rewritten as a normal invoke. - if (superTarget != null && !superTarget.getAccessFlags().isFinal()) { - return InvokeRetargetingResult.createInvokeRetargetingResult( - appView.options().desugaredLibrarySpecification.retargetMethod(superTarget, appView)); + if (cfInvoke.isInvokeSuper(context.getHolderType())) { + DexClassAndMethod superTarget = appInfo.lookupSuperTarget(invokedMethod, context); + if (superTarget != null) { + return computeSuperRetarget(superTarget.getDefinition()); } } return retarget; } - private InvokeRetargetingResult computeRetargetedMethod( - DexMethod invokedMethod, boolean isInterface) { - InvokeRetargetingResult invokeRetargetingResult = computeRetargetLibraryMember(invokedMethod); - if (!invokeRetargetingResult.hasNewInvokeTarget()) { - if (!matchesNonFinalHolderRewrite(invokedMethod)) { - return NO_REWRITING; - } - // We need to force resolution, even on d8, to know if the invoke has to be rewritten. - MethodResolutionResult resolutionResult = - appView.appInfoForDesugaring().resolveMethod(invokedMethod, isInterface); - if (resolutionResult.isFailedResolution()) { - return NO_REWRITING; - } - DexEncodedMethod singleTarget = resolutionResult.getSingleTarget(); - assert singleTarget != null; - invokeRetargetingResult = computeRetargetLibraryMember(singleTarget.getReference()); - } - return invokeRetargetingResult; - } - - private InvokeRetargetingResult computeRetargetLibraryMember(DexMethod method) { - DexClassAndMethod emulatedMethod = emulatedDispatchMethods.get(method); - if (emulatedMethod != null) { - assert !emulatedMethod.getAccessFlags().isStatic(); + private InvokeRetargetingResult computeNonStaticRetarget(DexEncodedMethod singleTarget) { + assert !singleTarget.isStatic(); + DexMethod reference = singleTarget.getReference(); + EmulatedDispatchMethodDescriptor descriptor = emulatedVirtualRetarget.get(reference); + if (descriptor != null) { return new InvokeRetargetingResult( true, - eventConsumer -> { - DexType newHolder = - syntheticHelper.ensureEmulatedHolderDispatchMethod(emulatedMethod, eventConsumer) - .type; - return computeRetargetMethod( - method, emulatedMethod.getAccessFlags().isStatic(), newHolder); - }); + eventConsumer -> + syntheticHelper.ensureEmulatedHolderDispatchMethod(descriptor, eventConsumer)); } - return InvokeRetargetingResult.createInvokeRetargetingResult(retargetLibraryMember.get(method)); + return InvokeRetargetingResult.createInvokeRetargetingResult( + nonEmulatedVirtualRetarget.get(reference)); } - private boolean matchesNonFinalHolderRewrite(DexMethod method) { - List<DexMethod> dexMethods = nonFinalHolderRewrites.get(method.name); - if (dexMethods == null) { - return false; + private InvokeRetargetingResult computeSuperRetarget(DexEncodedMethod singleTarget) { + assert !singleTarget.isStatic(); + DexMethod reference = singleTarget.getReference(); + EmulatedDispatchMethodDescriptor descriptor = emulatedVirtualRetarget.get(reference); + if (descriptor != null) { + return InvokeRetargetingResult.createInvokeRetargetingResult( + syntheticHelper.ensureForwardingMethod(descriptor)); } - for (DexMethod dexMethod : dexMethods) { - if (method.match(dexMethod)) { - return true; - } - } - return false; - } - - DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) { - DexItemFactory factory = appView.dexItemFactory(); - DexProto newProto = isStatic ? method.getProto() : factory.prependHolderToProto(method); - return factory.createMethod(newHolder, newProto, method.getName()); + return InvokeRetargetingResult.createInvokeRetargetingResult( + nonEmulatedVirtualRetarget.get(reference)); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java index d757c94..11fcfb7 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java
@@ -4,21 +4,22 @@ package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter; 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.ir.desugar.CfClassSynthesizerDesugaring; import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; -import com.android.tools.r8.utils.collections.DexClassAndMethodSet; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor; +import java.util.Map; public class DesugaredLibraryRetargeterL8Synthesizer implements CfClassSynthesizerDesugaring { private final AppView<?> appView; private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper; - private final DexClassAndMethodSet emulatedDispatchMethods; + private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedDispatchMethods; public static DesugaredLibraryRetargeterL8Synthesizer create( AppView<?> appView, RetargetingInfo retargetingInfo) { assert appView.options().isDesugaredLibraryCompilation(); - if (retargetingInfo == null || retargetingInfo.getEmulatedDispatchMethods().isEmpty()) { + if (retargetingInfo == null || retargetingInfo.getEmulatedVirtualRetarget().isEmpty()) { assert appView.options().desugaredLibrarySpecification.getRetargetCoreLibMember().isEmpty(); return null; } @@ -29,13 +30,14 @@ AppView<?> appView, RetargetingInfo retargetingInfo) { this.appView = appView; this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView); - emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods(); + emulatedDispatchMethods = retargetingInfo.getEmulatedVirtualRetarget(); } @Override public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) { assert !emulatedDispatchMethods.isEmpty(); - for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) { + for (EmulatedDispatchMethodDescriptor emulatedDispatchMethod : + emulatedDispatchMethods.values()) { syntheticHelper.ensureProgramEmulatedHolderDispatchMethod( emulatedDispatchMethod, eventConsumer); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java index aae08a7..414c32d 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexLibraryClass; import com.android.tools.r8.graph.DexMethod; @@ -15,15 +14,16 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring; import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor; import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer; import com.android.tools.r8.utils.OptionalBool; -import com.android.tools.r8.utils.collections.DexClassAndMethodSet; import com.google.common.collect.Maps; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -34,13 +34,13 @@ private final AppView<?> appView; private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper; - private final DexClassAndMethodSet emulatedDispatchMethods; + private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedDispatchMethods; public DesugaredLibraryRetargeterPostProcessor( AppView<?> appView, RetargetingInfo retargetingInfo) { this.appView = appView; this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView); - emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods(); + emulatedDispatchMethods = retargetingInfo.getEmulatedVirtualRetarget(); } @Override @@ -57,11 +57,12 @@ Collection<DexProgramClass> programClasses, DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer) { assert !appView.options().isDesugaredLibraryCompilation(); - Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap(); - for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) { - map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1)); - map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod); - } + Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap(); + emulatedDispatchMethods.forEach( + (method, descriptor) -> { + map.putIfAbsent(method.getHolderType(), new ArrayList<>(1)); + map.get(method.getHolderType()).add(method); + }); for (DexProgramClass clazz : programClasses) { if (clazz.superType == null) { assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString(); @@ -84,7 +85,9 @@ } private boolean inherit( - DexLibraryClass clazz, DexType typeToInherit, DexClassAndMethodSet retarget) { + DexLibraryClass clazz, + DexType typeToInherit, + Map<DexMethod, EmulatedDispatchMethodDescriptor> retarget) { DexLibraryClass current = clazz; while (current.type != appView.dexItemFactory().objectType) { if (current.type == typeToInherit) { @@ -92,7 +95,7 @@ } DexClass dexClass = appView.definitionFor(current.superType); if (dexClass == null || dexClass.isClasspathClass()) { - reportInvalidLibrarySupertype(current, retarget); + reportInvalidLibrarySupertype(current, retarget.keySet()); return false; } else if (dexClass.isProgramClass()) { // If dexClass is a program class, then it is already correctly desugared. @@ -106,7 +109,7 @@ private void ensureInterfacesAndForwardingMethodsSynthesized( DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer, DexProgramClass clazz, - List<DexClassAndMethod> methods) { + List<DexMethod> methods) { // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding // methods. // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor @@ -114,9 +117,10 @@ if (appView.isAlreadyLibraryDesugared(clazz)) { return; } - for (DexClassAndMethod method : methods) { + for (DexMethod method : methods) { + EmulatedDispatchMethodDescriptor descriptor = emulatedDispatchMethods.get(method); DexClass newInterface = - syntheticHelper.ensureEmulatedInterfaceDispatchMethod(method, eventConsumer); + syntheticHelper.ensureEmulatedInterfaceDispatchMethod(descriptor, eventConsumer); if (clazz.interfaces.contains(newInterface.type)) { // The class has already been desugared. continue; @@ -131,30 +135,35 @@ clazz.addExtraInterfaces( Collections.singletonList(new ClassTypeSignature(newInterface.type))); eventConsumer.acceptInterfaceInjection(clazz, newInterface); - if (clazz.lookupVirtualMethod(method.getReference()) == null) { - DexEncodedMethod newMethod = createForwardingMethod(method, clazz); + DexMethod itfMethod = + syntheticHelper.getEmulatedInterfaceDispatchMethod(newInterface, descriptor); + if (clazz.lookupVirtualMethod(method) == null) { + DexEncodedMethod newMethod = createForwardingMethod(itfMethod, descriptor, clazz); clazz.addVirtualMethod(newMethod); eventConsumer.acceptForwardingMethod(new ProgramMethod(clazz, newMethod)); } } } - private DexEncodedMethod createForwardingMethod(DexClassAndMethod target, DexClass clazz) { + private DexEncodedMethod createForwardingMethod( + DexMethod target, EmulatedDispatchMethodDescriptor descriptor, DexClass clazz) { // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar // even if this results in invalid code, these classes are never desugared. // In desugared library, emulated interface methods can be overridden by retarget lib members. - DexMethod forwardMethod = - appView.options().desugaredLibrarySpecification.retargetMethod(target, appView); - assert forwardMethod != null && forwardMethod != target.getReference(); + DexMethod forwardMethod = syntheticHelper.ensureForwardingMethod(descriptor); + assert forwardMethod != null && forwardMethod != target; + DexEncodedMethod resolvedMethod = + appView.appInfoForDesugaring().resolveMethod(target, true).getResolvedMethod(); + assert resolvedMethod != null; DexEncodedMethod desugaringForwardingMethod = DexEncodedMethod.createDesugaringForwardingMethod( - target, clazz, forwardMethod, appView.dexItemFactory()); + resolvedMethod, clazz, forwardMethod, appView.dexItemFactory()); desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE); return desugaringForwardingMethod; } private void reportInvalidLibrarySupertype( - DexLibraryClass libraryClass, DexClassAndMethodSet retarget) { + DexLibraryClass libraryClass, Set<DexMethod> retarget) { DexClass dexClass = appView.definitionFor(libraryClass.superType); String message; if (dexClass == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java index 1a46480..5a5768f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -5,19 +5,21 @@ import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.ClasspathOrLibraryClass; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor; import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer; import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider; import com.android.tools.r8.synthesis.SyntheticClassBuilder; import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; import com.android.tools.r8.utils.DescriptorUtils; -import java.util.Collections; +import java.util.LinkedHashMap; public class DesugaredLibraryRetargeterSyntheticHelper { @@ -27,54 +29,87 @@ this.appView = appView; } - public DexClass ensureEmulatedHolderDispatchMethod( - DexClassAndMethod emulatedDispatchMethod, + public DexMethod ensureForwardingMethod(EmulatedDispatchMethodDescriptor descriptor) { + // TODO(b/184026720): We may synthesize a stub on the classpath if absent. + assert descriptor.getForwardingMethod().getHolderKind() == null; + return descriptor.getForwardingMethod().getMethod(); + } + + private DexMethod emulatedHolderDispatchMethod(DexType holder, DerivedMethod method) { + assert method.getHolderKind() == SyntheticKind.RETARGET_CLASS; + return appView.dexItemFactory().createMethod(holder, method.getProto(), method.getName()); + } + + DexMethod emulatedInterfaceDispatchMethod(DexType holder, DerivedMethod method) { + assert method.getHolderKind() == SyntheticKind.RETARGET_INTERFACE; + return appView.dexItemFactory().createMethod(holder, method.getProto(), method.getName()); + } + + public DexMethod getEmulatedInterfaceDispatchMethod( + DexClass newInterface, EmulatedDispatchMethodDescriptor descriptor) { + DexMethod method = + emulatedInterfaceDispatchMethod(newInterface.type, descriptor.getInterfaceMethod()); + assert newInterface.lookupMethod(method) != null; + return method; + } + + public DexMethod ensureEmulatedHolderDispatchMethod( + EmulatedDispatchMethodDescriptor descriptor, DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) { assert eventConsumer != null; + DerivedMethod emulatedDispatchMethod = descriptor.getEmulatedDispatchMethod(); + DexClass holderContext = + appView.contextIndependentDefinitionFor(emulatedDispatchMethod.getHolderContext()); + DexClass syntheticClass; if (appView.options().isDesugaredLibraryCompilation()) { - return appView - .getSyntheticItems() - .getExistingFixedClass( - SyntheticKind.RETARGET_CLASS, emulatedDispatchMethod.getHolder(), appView); + syntheticClass = + appView + .getSyntheticItems() + .getExistingFixedClass( + emulatedDispatchMethod.getHolderKind(), holderContext, appView); + DexMethod dispatchMethod = + emulatedHolderDispatchMethod(syntheticClass.type, emulatedDispatchMethod); + assert syntheticClass.lookupMethod(dispatchMethod) != null; + return dispatchMethod; + } else { + DexClass itfClass = ensureEmulatedInterfaceDispatchMethod(descriptor, eventConsumer); + ClasspathOrLibraryClass context = holderContext.asClasspathOrLibraryClass(); + assert context != null; + syntheticClass = + appView + .getSyntheticItems() + .ensureFixedClasspathClass( + SyntheticKind.RETARGET_CLASS, + context, + appView, + classBuilder -> buildHolderDispatchMethod(classBuilder, itfClass, descriptor), + clazz -> { + eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz); + rewriteType(clazz.type); + }); } - DexClass interfaceClass = - ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer); - DexMethod itfMethod = - interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference(); - ClasspathOrLibraryClass context = - emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass(); - assert context != null; - return appView - .getSyntheticItems() - .ensureFixedClasspathClass( - SyntheticKind.RETARGET_CLASS, - context, - appView, - classBuilder -> - buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod), - clazz -> { - eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz); - rewriteType(clazz.type); - }); + DexMethod dispatchMethod = + emulatedHolderDispatchMethod(syntheticClass.type, emulatedDispatchMethod); + assert syntheticClass.lookupMethod(dispatchMethod) != null; + return dispatchMethod; } public void ensureProgramEmulatedHolderDispatchMethod( - DexClassAndMethod emulatedDispatchMethod, + EmulatedDispatchMethodDescriptor descriptor, DesugaredLibraryRetargeterL8SynthesizerEventConsumer eventConsumer) { assert eventConsumer != null; assert appView.options().isDesugaredLibraryCompilation(); - DexClass interfaceClass = - ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer); - DexMethod itfMethod = - interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference(); + DerivedMethod emulatedDispatchMethod = descriptor.getEmulatedDispatchMethod(); + DexClass holderContext = + appView.contextIndependentDefinitionFor(emulatedDispatchMethod.getHolderContext()); + DexClass itfClass = ensureEmulatedInterfaceDispatchMethod(descriptor, eventConsumer); appView .getSyntheticItems() .ensureFixedClass( - SyntheticKind.RETARGET_CLASS, - emulatedDispatchMethod.getHolder(), + emulatedDispatchMethod.getHolderKind(), + holderContext, appView, - classBuilder -> - buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod), + classBuilder -> buildHolderDispatchMethod(classBuilder, itfClass, descriptor), clazz -> { eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz); rewriteType(clazz.type); @@ -82,17 +117,17 @@ } public DexClass ensureEmulatedInterfaceDispatchMethod( - DexClassAndMethod emulatedDispatchMethod, + EmulatedDispatchMethodDescriptor descriptor, DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) { assert eventConsumer != null; + DerivedMethod itfMethod = descriptor.getInterfaceMethod(); + DexClass itfContext = appView.contextIndependentDefinitionFor(itfMethod.getHolderContext()); if (appView.options().isDesugaredLibraryCompilation()) { return appView .getSyntheticItems() - .getExistingFixedClass( - SyntheticKind.RETARGET_INTERFACE, emulatedDispatchMethod.getHolder(), appView); + .getExistingFixedClass(itfMethod.getHolderKind(), itfContext, appView); } - ClasspathOrLibraryClass context = - emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass(); + ClasspathOrLibraryClass context = itfContext.asClasspathOrLibraryClass(); assert context != null; return appView .getSyntheticItems() @@ -100,7 +135,7 @@ SyntheticKind.RETARGET_INTERFACE, context, appView, - classBuilder -> buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod), + classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor), clazz -> { eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz); rewriteType(clazz.type); @@ -108,17 +143,19 @@ } public DexClass ensureEmulatedInterfaceDispatchMethod( - DexClassAndMethod emulatedDispatchMethod, + EmulatedDispatchMethodDescriptor descriptor, DesugaredLibraryRetargeterL8SynthesizerEventConsumer eventConsumer) { assert appView.options().isDesugaredLibraryCompilation(); assert eventConsumer != null; + DerivedMethod itfMethod = descriptor.getInterfaceMethod(); + DexClass itfContext = appView.contextIndependentDefinitionFor(itfMethod.getHolderContext()); return appView .getSyntheticItems() .ensureFixedClass( - SyntheticKind.RETARGET_INTERFACE, - emulatedDispatchMethod.getHolder(), + itfMethod.getHolderKind(), + itfContext, appView, - classBuilder -> buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod), + classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor), clazz -> { eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz); rewriteType(clazz.type); @@ -126,18 +163,21 @@ } private void buildInterfaceDispatchMethod( - SyntheticClassBuilder<?, ?> classBuilder, DexClassAndMethod emulatedDispatchMethod) { + SyntheticClassBuilder<?, ?> classBuilder, EmulatedDispatchMethodDescriptor descriptor) { classBuilder .setInterface() .addMethod( methodBuilder -> { + DexMethod itfMethod = + emulatedInterfaceDispatchMethod( + classBuilder.getType(), descriptor.getInterfaceMethod()); MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags( Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false); methodBuilder - .setName(emulatedDispatchMethod.getName()) - .setProto(emulatedDispatchMethod.getProto()) + .setName(itfMethod.getName()) + .setProto(itfMethod.getProto()) // Will be traced by the enqueuer. .disableAndroidApiLevelCheck() .setAccessFlags(flags); @@ -145,36 +185,36 @@ } private <SCB extends SyntheticClassBuilder<?, ?>> void buildHolderDispatchMethod( - SCB classBuilder, DexClassAndMethod emulatedDispatchMethod, DexMethod itfMethod) { + SCB classBuilder, DexClass itfClass, EmulatedDispatchMethodDescriptor descriptor) { classBuilder.addMethod( methodBuilder -> { - DexMethod desugarMethod = - appView - .options() - .desugaredLibrarySpecification - .retargetMethod(emulatedDispatchMethod, appView); - assert desugarMethod - != null; // This method is reached only for retarget core lib members. + DexMethod dispatchMethod = + emulatedHolderDispatchMethod( + classBuilder.getType(), descriptor.getEmulatedDispatchMethod()); methodBuilder - .setName(emulatedDispatchMethod.getName()) - .setProto(desugarMethod.proto) + .setName(dispatchMethod.getName()) + .setProto(dispatchMethod.getProto()) .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) // Will be traced by the enqueuer. .disableAndroidApiLevelCheck() .setCode( methodSig -> appView.options().isDesugaredLibraryCompilation() - ? new EmulateDispatchSyntheticCfCodeProvider( - methodSig.getHolderType(), - desugarMethod, - itfMethod, - Collections.emptyList(), - appView) - .generateCfCode() + ? generateEmulatedDispatchCfCode(descriptor, itfClass, methodSig) : null); }); } + private CfCode generateEmulatedDispatchCfCode( + EmulatedDispatchMethodDescriptor descriptor, DexClass itfClass, DexMethod methodSig) { + DexMethod forwardingMethod = ensureForwardingMethod(descriptor); + DexMethod itfMethod = getEmulatedInterfaceDispatchMethod(itfClass, descriptor); + assert descriptor.getDispatchCases().isEmpty(); + return new EmulateDispatchSyntheticCfCodeProvider( + methodSig.getHolderType(), forwardingMethod, itfMethod, new LinkedHashMap<>(), appView) + .generateCfCode(); + } + private void rewriteType(DexType type) { String newName = appView.options().desugaredLibrarySpecification.convertJavaNameToDesugaredLibrary(type);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java index d9656fe..eb38e9e 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
@@ -12,55 +12,64 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags; +import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; import com.android.tools.r8.utils.WorkList; -import com.android.tools.r8.utils.collections.DexClassAndMethodSet; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class RetargetingInfo { - private final Map<DexMethod, DexMethod> retargetLibraryMember; - // Map nonFinalRewrite hold a methodName -> method mapping for methods which are rewritten while - // the holder is non final. In this case d8 needs to force resolution of given methods to see if - // the invoke needs to be rewritten. - private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites; - // Non final virtual library methods requiring generation of emulated dispatch. - private final DexClassAndMethodSet emulatedDispatchMethods; + private final Map<DexMethod, DexMethod> staticRetarget; + private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget; + private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget; RetargetingInfo( - Map<DexMethod, DexMethod> retargetLibraryMember, - Map<DexString, List<DexMethod>> nonFinalHolderRewrites, - DexClassAndMethodSet emulatedDispatchMethods) { - this.retargetLibraryMember = retargetLibraryMember; - this.nonFinalHolderRewrites = nonFinalHolderRewrites; - this.emulatedDispatchMethods = emulatedDispatchMethods; + Map<DexMethod, DexMethod> staticRetarget, + Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget, + Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) { + this.staticRetarget = staticRetarget; + this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget; + this.emulatedVirtualRetarget = emulatedVirtualRetarget; } - public static synchronized RetargetingInfo get(AppView<?> appView) { + public static RetargetingInfo get(AppView<?> appView) { + if (appView.options().testing.machineDesugaredLibrarySpecification != null) { + MachineRewritingFlags rewritingFlags = + appView.options().testing.machineDesugaredLibrarySpecification.getRewritingFlags(); + return new RetargetingInfo( + rewritingFlags.getStaticRetarget(), + rewritingFlags.getNonEmulatedVirtualRetarget(), + rewritingFlags.getEmulatedVirtualRetarget()); + } return new RetargetingInfoBuilder(appView).computeRetargetingInfo(); } - public Map<DexMethod, DexMethod> getRetargetLibraryMember() { - return retargetLibraryMember; + public Map<DexMethod, DexMethod> getStaticRetarget() { + return staticRetarget; } - public Map<DexString, List<DexMethod>> getNonFinalHolderRewrites() { - return nonFinalHolderRewrites; + public Map<DexMethod, DexMethod> getNonEmulatedVirtualRetarget() { + return nonEmulatedVirtualRetarget; } - public DexClassAndMethodSet getEmulatedDispatchMethods() { - return emulatedDispatchMethods; + public Map<DexMethod, EmulatedDispatchMethodDescriptor> getEmulatedVirtualRetarget() { + return emulatedVirtualRetarget; } private static class RetargetingInfoBuilder { private final AppView<?> appView; - private final Map<DexMethod, DexMethod> retargetLibraryMember = new IdentityHashMap<>(); - private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites = new IdentityHashMap<>(); - private final DexClassAndMethodSet emulatedDispatchMethods = DexClassAndMethodSet.create(); + private final Map<DexMethod, DexMethod> staticRetarget = new IdentityHashMap<>(); + private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget = new IdentityHashMap<>(); + private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget = + new IdentityHashMap<>(); public RetargetingInfoBuilder(AppView<?> appView) { this.appView = appView; @@ -72,8 +81,7 @@ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = desugaredLibrarySpecification.getRetargetCoreLibMember(); if (retargetCoreLibMember.isEmpty()) { - return new RetargetingInfo( - ImmutableMap.of(), ImmutableMap.of(), DexClassAndMethodSet.empty()); + return new RetargetingInfo(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of()); } for (DexString methodName : retargetCoreLibMember.keySet()) { for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) { @@ -82,28 +90,41 @@ DexType newHolder = retargetCoreLibMember.get(methodName).get(inType); List<DexClassAndMethod> found = findMethodsWithName(methodName, typeClass); for (DexClassAndMethod method : found) { - boolean emulatedDispatch = false; DexMethod methodReference = method.getReference(); - if (!typeClass.isFinal()) { - nonFinalHolderRewrites.putIfAbsent(method.getName(), new ArrayList<>()); - nonFinalHolderRewrites.get(method.getName()).add(methodReference); - if (!method.getAccessFlags().isStatic()) { - if (isEmulatedInterfaceDispatch(method)) { - // In this case interface method rewriter takes care of it. - continue; - } else if (!method.getAccessFlags().isFinal()) { - // Virtual rewrites require emulated dispatch for inheritance. - // The call is rewritten to the dispatch holder class instead. - emulatedDispatchMethods.add(method); - emulatedDispatch = true; - } - } - } - if (!emulatedDispatch) { - retargetLibraryMember.put( + if (method.getAccessFlags().isStatic()) { + staticRetarget.put( methodReference, computeRetargetMethod( methodReference, method.getAccessFlags().isStatic(), newHolder)); + continue; + } + if (isEmulatedInterfaceDispatch(method)) { + continue; + } + if (typeClass.isFinal() || method.getAccessFlags().isFinal()) { + nonEmulatedVirtualRetarget.put( + methodReference, + computeRetargetMethod( + methodReference, method.getAccessFlags().isStatic(), newHolder)); + } else { + // Virtual rewrites require emulated dispatch for inheritance. + // The call is rewritten to the dispatch holder class instead. + DexProto newProto = appView.dexItemFactory().prependHolderToProto(methodReference); + DexMethod forwardingDexMethod = + appView.dexItemFactory().createMethod(newHolder, newProto, methodName); + DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod); + DerivedMethod interfaceMethod = + new DerivedMethod(methodReference, SyntheticKind.RETARGET_INTERFACE); + DexMethod dispatchDexMethod = + appView + .dexItemFactory() + .createMethod(methodReference.getHolderType(), newProto, methodName); + DerivedMethod dispatchMethod = + new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS); + emulatedVirtualRetarget.put( + methodReference, + new EmulatedDispatchMethodDescriptor( + interfaceMethod, dispatchMethod, forwardingMethod, new LinkedHashMap<>())); } } } @@ -123,7 +144,7 @@ DexMethod target = computeRetargetMethod( source, true, itemFactory.createType("Ljava/util/DesugarArrays;")); - retargetLibraryMember.put(source, target); + staticRetarget.put(source, target); // TODO(b/181629049): This is only a workaround rewriting invokes of // j.u.TimeZone.getTimeZone taking a java.time.ZoneId. name = itemFactory.createString("getTimeZone"); @@ -136,12 +157,12 @@ target = computeRetargetMethod( source, true, itemFactory.createType("Ljava/util/DesugarTimeZone;")); - retargetLibraryMember.put(source, target); + staticRetarget.put(source, target); } return new RetargetingInfo( - ImmutableMap.copyOf(retargetLibraryMember), - ImmutableMap.copyOf(nonFinalHolderRewrites), - emulatedDispatchMethods); + ImmutableMap.copyOf(staticRetarget), + ImmutableMap.copyOf(nonEmulatedVirtualRetarget), + emulatedVirtualRetarget); } DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java new file mode 100644 index 0000000..d2227cb --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -0,0 +1,211 @@ +// 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.ir.desugar.desugaredlibrary.specificationconversion; + +import com.android.tools.r8.dex.ApplicationReader; +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProto; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.MethodResolutionResult; +import com.android.tools.r8.graph.SubtypingInfo; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags; +import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import com.android.tools.r8.utils.TraversalContinuation; +import java.io.IOException; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.function.BiConsumer; + +public class HumanToMachineSpecificationConverter { + + public MachineDesugaredLibrarySpecification convert( + HumanDesugaredLibrarySpecification humanSpec, Path androidLib, InternalOptions options) + throws IOException { + DexApplication app = readApp(androidLib, options); + AppView<?> appView = AppView.createForD8(AppInfo.createInitialAppInfo(app)); + MachineRewritingFlags machineRewritingFlags = + convertRewritingFlags(humanSpec.getRewritingFlags(), appView.appInfoForDesugaring()); + return new MachineDesugaredLibrarySpecification( + humanSpec.isLibraryCompilation(), machineRewritingFlags); + } + + private MachineRewritingFlags convertRewritingFlags( + HumanRewritingFlags rewritingFlags, AppInfoWithClassHierarchy appInfo) { + MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder(); + SubtypingInfo subtypingInfo = new SubtypingInfo(appInfo); + rewritingFlags + .getRetargetCoreLibMember() + .forEach( + (method, type) -> + convertRetargetCoreLibMemberFlag( + builder, rewritingFlags, method, type, appInfo, subtypingInfo)); + return builder.build(); + } + + private void convertRetargetCoreLibMemberFlag( + MachineRewritingFlags.Builder builder, + HumanRewritingFlags rewritingFlags, + DexMethod method, + DexType type, + AppInfoWithClassHierarchy appInfo, + SubtypingInfo subtypingInfo) { + DexClass holder = appInfo.definitionFor(method.holder); + DexEncodedMethod foundMethod = holder.lookupMethod(method); + assert foundMethod != null; + if (foundMethod.isStatic()) { + convertStaticRetarget(builder, foundMethod, type, appInfo, subtypingInfo); + return; + } + if (holder.isFinal() || foundMethod.isFinal()) { + convertNonEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo); + return; + } + convertEmulatedVirtualRetarget( + builder, rewritingFlags, foundMethod, type, appInfo, subtypingInfo); + } + + private void convertEmulatedVirtualRetarget( + MachineRewritingFlags.Builder builder, + HumanRewritingFlags rewritingFlags, + DexEncodedMethod src, + DexType type, + AppInfoWithClassHierarchy appInfo, + SubtypingInfo subtypingInfo) { + if (isEmulatedInterfaceDispatch(src, appInfo, rewritingFlags)) { + // Handled by emulated interface dispatch. + return; + } + // TODO(b/184026720): Implement library boundaries. + DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference()); + DexMethod forwardingDexMethod = + appInfo.dexItemFactory().createMethod(type, newProto, src.getName()); + DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod); + DerivedMethod interfaceMethod = + new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_INTERFACE); + DexMethod dispatchDexMethod = + appInfo.dexItemFactory().createMethod(src.getHolderType(), newProto, src.getName()); + DerivedMethod dispatchMethod = + new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS); + LinkedHashMap<DexType, DerivedMethod> dispatchCases = new LinkedHashMap<>(); + assert validateNoOverride(src, appInfo, subtypingInfo); + builder.putEmulatedVirtualRetarget( + src.getReference(), + new EmulatedDispatchMethodDescriptor( + interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases)); + } + + private boolean validateNoOverride( + DexEncodedMethod src, AppInfoWithClassHierarchy appInfo, SubtypingInfo subtypingInfo) { + for (DexType subtype : subtypingInfo.subtypes(src.getHolderType())) { + DexClass subclass = appInfo.definitionFor(subtype); + MethodResolutionResult resolutionResult = + appInfo.resolveMethodOn(subclass, src.getReference()); + if (resolutionResult.isSuccessfulMemberResolutionResult() + && resolutionResult.getResolvedMethod().getReference() != src.getReference()) { + assert false; // Unsupported. + } + } + return true; + } + + private boolean isEmulatedInterfaceDispatch( + DexEncodedMethod method, + AppInfoWithClassHierarchy appInfo, + HumanRewritingFlags humanRewritingFlags) { + // Answers true if this method is already managed through emulated interface dispatch. + Map<DexType, DexType> emulateLibraryInterface = + humanRewritingFlags.getEmulateLibraryInterface(); + if (emulateLibraryInterface.isEmpty()) { + return false; + } + DexMethod methodToFind = method.getReference(); + // Look-up all superclass and interfaces, if an emulated interface is found, + // and it implements the method, answers true. + DexClass dexClass = appInfo.definitionFor(method.getHolderType()); + // Cannot retarget a method on a virtual method on an emulated interface. + assert !emulateLibraryInterface.containsKey(dexClass.getType()); + return appInfo + .traverseSuperTypes( + dexClass, + (supertype, subclass, isSupertypeAnInterface) -> + TraversalContinuation.breakIf( + subclass.isInterface() + && emulateLibraryInterface.containsKey(subclass.getType()) + && subclass.lookupMethod(methodToFind) != null)) + .shouldBreak(); + } + + private void convertNonEmulatedRetarget( + DexEncodedMethod foundMethod, + DexType type, + AppInfoWithClassHierarchy appInfo, + SubtypingInfo subtypingInfo, + BiConsumer<DexMethod, DexMethod> consumer) { + DexMethod src = foundMethod.getReference(); + DexMethod dest = src.withHolder(type, appInfo.dexItemFactory()); + consumer.accept(src, dest); + for (DexType subtype : subtypingInfo.subtypes(foundMethod.getHolderType())) { + DexClass subclass = appInfo.definitionFor(subtype); + MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(subclass, src); + if (resolutionResult.isSuccessfulMemberResolutionResult() + && resolutionResult.getResolvedMethod().getReference() == src) { + consumer.accept(src.withHolder(subtype, appInfo.dexItemFactory()), dest); + } + } + } + + private void convertNonEmulatedVirtualRetarget( + MachineRewritingFlags.Builder builder, + DexEncodedMethod foundMethod, + DexType type, + AppInfoWithClassHierarchy appInfo, + SubtypingInfo subtypingInfo) { + convertNonEmulatedRetarget( + foundMethod, + type, + appInfo, + subtypingInfo, + (src, dest) -> + builder.putNonEmulatedVirtualRetarget( + src, + dest.withExtraArgumentPrepended( + foundMethod.getHolderType(), appInfo.dexItemFactory()))); + } + + private void convertStaticRetarget( + MachineRewritingFlags.Builder builder, + DexEncodedMethod foundMethod, + DexType type, + AppInfoWithClassHierarchy appInfo, + SubtypingInfo subtypingInfo) { + convertNonEmulatedRetarget( + foundMethod, type, appInfo, subtypingInfo, builder::putStaticRetarget); + } + + private DexApplication readApp(Path androidLib, InternalOptions options) throws IOException { + AndroidApp androidApp = AndroidApp.builder().addProgramFile(androidLib).build(); + ApplicationReader applicationReader = + new ApplicationReader(androidApp, options, Timing.empty()); + ExecutorService executorService = ThreadUtils.getExecutorService(options); + return applicationReader.read(executorService).toDirect(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java index 14898fc..34bf5fa 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -15,7 +15,6 @@ import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification; import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags; import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags; @@ -43,9 +42,8 @@ import java.util.Map; import java.util.concurrent.ExecutorService; -public class LegacyToHumanSpecificationConverter implements SpecificationConverter { +public class LegacyToHumanSpecificationConverter { - @Override public void convertAllAPILevels( StringResource inputSpecification, Path androidLib, StringConsumer output) throws IOException { @@ -136,8 +134,7 @@ libraryFlags.put(level, builder.build()); } - private DirectMappedDexApplication readApp(Path androidLib, InternalOptions options) - throws IOException { + private DexApplication readApp(Path androidLib, InternalOptions options) throws IOException { AndroidApp androidApp = AndroidApp.builder().addLibraryFile(androidLib).build(); ApplicationReader applicationReader = new ApplicationReader(androidApp, options, Timing.empty());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java deleted file mode 100644 index 85a8c8b..0000000 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java +++ /dev/null
@@ -1,16 +0,0 @@ -// Copyright (c) 2021, 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.desugar.desugaredlibrary.specificationconversion; - -import com.android.tools.r8.StringConsumer; -import com.android.tools.r8.StringResource; -import java.io.IOException; -import java.nio.file.Path; - -public interface SpecificationConverter { - - void convertAllAPILevels( - StringResource inputSpecification, Path androidLib, StringConsumer output) throws IOException; -}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java index 0d79ac1..3a91fef 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -853,7 +853,7 @@ : appView.options().desugaredLibrarySpecification.retargetMethod(target, appView); DexEncodedMethod desugaringForwardingMethod = DexEncodedMethod.createDesugaringForwardingMethod( - target, clazz, forwardMethod, dexItemFactory); + target.getDefinition(), clazz, forwardMethod, dexItemFactory); if (!target.isProgramDefinition() || target.getDefinition().isLibraryMethodOverride().isTrue()) { desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java index 3d91f5c..55cf273 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -20,7 +20,6 @@ import com.android.tools.r8.synthesis.SyntheticMethodBuilder; import com.android.tools.r8.synthesis.SyntheticNaming; import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder; -import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; @@ -28,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -143,7 +143,7 @@ .withHolder(helper.getEmulatedInterface(theInterface.type), appView.dexItemFactory()); DexMethod companionMethod = helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference(); - List<Pair<DexType, DexMethod>> extraDispatchCases = + LinkedHashMap<DexType, DexMethod> extraDispatchCases = getDispatchCases(method, theInterface, companionMethod); return new EmulateDispatchSyntheticCfCodeProvider( emulatedInterfaceMethod.getHolderType(), @@ -154,14 +154,14 @@ .generateCfCode(); } - private List<Pair<DexType, DexMethod>> getDispatchCases( + private LinkedHashMap<DexType, DexMethod> getDispatchCases( ProgramMethod method, DexProgramClass theInterface, DexMethod companionMethod) { // To properly emulate the library interface call, we need to compute the interfaces // inheriting from the interface and manually implement the dispatch with instance of. // The list guarantees that an interface is always after interfaces it extends, // hence reverse iteration. List<DexType> subInterfaces = emulatedInterfacesHierarchy.get(theInterface.type); - List<Pair<DexType, DexMethod>> extraDispatchCases = new ArrayList<>(); + LinkedHashMap<DexType, DexMethod> extraDispatchCases = new LinkedHashMap<>(); // In practice, there is usually a single case (except for tests), // so we do not bother to make the following loop more clever. Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = @@ -171,17 +171,16 @@ for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) { DexClass inClass = appView.definitionFor(inType); if (inClass != null && implementsInterface(inClass, theInterface.type)) { - extraDispatchCases.add( - new Pair<>( - inType, - appView - .dexItemFactory() - .createMethod( - retargetCoreLibMember.get(methodName).get(inType), - appView - .dexItemFactory() - .protoWithDifferentFirstParameter(companionMethod.proto, inType), - method.getName()))); + extraDispatchCases.put( + inType, + appView + .dexItemFactory() + .createMethod( + retargetCoreLibMember.get(methodName).get(inType), + appView + .dexItemFactory() + .protoWithDifferentFirstParameter(companionMethod.proto, inType), + method.getName())); } } } @@ -196,11 +195,10 @@ DexEncodedMethod result = subInterfaceClass.lookupVirtualMethod(method.getReference()); if (result != null && !result.isAbstract()) { assert result.isDefaultMethod(); - extraDispatchCases.add( - new Pair<>( - subInterfaceClass.type, - InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass( - result.getReference(), appView.dexItemFactory()))); + extraDispatchCases.put( + subInterfaceClass.type, + InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass( + result.getReference(), appView.dexItemFactory())); } } } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java index e7edb01..1b9d97b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -10,6 +10,8 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; @@ -62,6 +64,11 @@ } @Override + public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) { + // Intentionally empty. + } + + @Override public void unboxEnums( AppView<AppInfoWithLiveness> appView, IRConverter converter,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java index 659e685..4179fed 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -10,6 +10,8 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; @@ -44,6 +46,8 @@ public abstract Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor); + public abstract void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke); + public abstract void unboxEnums( AppView<AppInfoWithLiveness> appView, IRConverter converter,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index d35c7b7..acde184 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -62,6 +62,7 @@ import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InvokeCustom; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeStatic; @@ -1457,6 +1458,13 @@ } @Override + public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) { + if (enumUnboxerRewriter != null) { + enumUnboxerRewriter.rewriteNullCheck(iterator, invoke); + } + } + + @Override public void unsetRewriter() { enumUnboxerRewriter = null; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java index 5fc743e..787b4da 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -148,9 +148,7 @@ continue; } } else if (invokedMethod == factory.objectMembers.getClass) { - assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers(); - replaceEnumInvoke( - iterator, invoke, getSharedUtilityClass().ensureCheckNotZeroMethod(appView)); + rewriteNullCheck(iterator, invoke); continue; } } else if (invokedMethod == factory.stringBuilderMethods.appendObject @@ -346,10 +344,7 @@ Value argument = invoke.getFirstArgument(); DexType enumType = getEnumTypeOrNull(argument, convertedEnums); if (enumType != null) { - replaceEnumInvoke( - instructionIterator, - invoke, - getSharedUtilityClass().ensureCheckNotZeroMethod(appView)); + rewriteNullCheck(instructionIterator, invoke); } } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) { assert invoke.arguments().size() == 2; @@ -434,6 +429,11 @@ } } + public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) { + assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers(); + replaceEnumInvoke(iterator, invoke, getSharedUtilityClass().ensureCheckNotZeroMethod(appView)); + } + private void removeRedundantValuesArrayCloning( InvokeStatic invoke, Set<Instruction> instructionsToRemove, Set<BasicBlock> seenBlocks) { for (Instruction user : invoke.outValue().aliasedUsers()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java index 9997cc0..862840b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -43,6 +43,7 @@ import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData; import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification; import com.android.tools.r8.ir.optimize.enums.code.CheckNotZeroCode; +import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore; import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo; @@ -189,10 +190,10 @@ .setApiLevelForCode(appView.computedMinApiLevel()) .setCode(method -> new CheckNotZeroCode(checkNotNullMethod)) .setOptimizationInfo( - checkNotNullMethod - .getOptimizationInfo() - .asMutableMethodOptimizationInfo() - .mutableCopy()) + DefaultMethodOptimizationInfo.getInstance() + .toMutableOptimizationInfo() + .setEnumUnboxerMethodClassification( + checkNotNullClassification)) .setProto(newProto)); checkNotNullToCheckNotZeroMapping.put( checkNotNullMethod.getReference(), checkNotZeroMethod.getReference());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java index 6356bbf..b651b1f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -24,7 +24,7 @@ // The index exactly matches with in values of invocation, i.e., even including receiver. public DynamicType getDynamicType(int argIndex) { - return null; + return DynamicType.unknown(); } // The index exactly matches with in values of invocation, i.e., even including receiver.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java index 2cb900b..e07fee5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -282,12 +282,13 @@ return enumUnboxerMethodClassification; } - void setEnumUnboxerMethodClassification( + public MutableMethodOptimizationInfo setEnumUnboxerMethodClassification( EnumUnboxerMethodClassification enumUnboxerMethodClassification) { // Check monotonicity. assert !this.enumUnboxerMethodClassification.isCheckNotNullClassification() || enumUnboxerMethodClassification.isCheckNotNullClassification(); this.enumUnboxerMethodClassification = enumUnboxerMethodClassification; + return this; } public void unsetEnumUnboxerMethodClassification() {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java index 4847e78..e356796 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -21,29 +21,30 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.ValueType; -import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.collections.ImmutableDeque; import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.objectweb.asm.Opcodes; public class EmulateDispatchSyntheticCfCodeProvider extends SyntheticCfCodeProvider { - private final DexType receiverType; private final DexMethod forwardingMethod; + private final DexType receiverType; private final DexMethod interfaceMethod; - private final List<Pair<DexType, DexMethod>> extraDispatchCases; + private final LinkedHashMap<DexType, DexMethod> extraDispatchCases; public EmulateDispatchSyntheticCfCodeProvider( DexType holder, DexMethod forwardingMethod, DexMethod interfaceMethod, - List<Pair<DexType, DexMethod>> extraDispatchCases, + LinkedHashMap<DexType, DexMethod> extraDispatchCases, AppView<?> appView) { super(appView, holder); - this.receiverType = forwardingMethod.getParameter(0); this.forwardingMethod = forwardingMethod; + this.receiverType = forwardingMethod.getParameter(0); this.interfaceMethod = interfaceMethod; this.extraDispatchCases = extraDispatchCases; } @@ -78,19 +79,19 @@ addReturn(instructions); // SubInterface dispatch (subInterfaces are ordered). - for (Pair<DexType, DexMethod> dispatch : extraDispatchCases) { + for (Map.Entry<DexType, DexMethod> dispatch : extraDispatchCases.entrySet()) { // Type check basic block. instructions.add(labels[nextLabel++]); instructions.add(new CfFrame(locals, ImmutableDeque.of())); instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0)); - instructions.add(new CfInstanceOf(dispatch.getFirst())); + instructions.add(new CfInstanceOf(dispatch.getKey())); instructions.add(new CfIf(If.Type.EQ, ValueType.INT, labels[nextLabel])); // Call basic block. instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0)); - instructions.add(new CfCheckCast(dispatch.getFirst())); + instructions.add(new CfCheckCast(dispatch.getKey())); loadExtraParameters(instructions); - instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, dispatch.getSecond(), false)); + instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, dispatch.getValue(), false)); addReturn(instructions); }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java index ea756b5..dce1aee 100644 --- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java +++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -143,6 +143,9 @@ return iterator; } Value in = instruction.value(); + if (in.isDexItemBasedConstString()) { + return iterator; + } if (!in.isConstString()) { warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, null); return iterator;
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java index cd6c271..8a8d804 100644 --- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -11,7 +11,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.MethodAccessInfoCollection; import com.android.tools.r8.graph.MethodResolutionResult; -import com.android.tools.r8.graph.SubtypingInfo; +import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ThreadUtils; @@ -123,7 +123,6 @@ } private final AppView<AppInfoWithLiveness> appView; - private final SubtypingInfo subtypingInfo; private final MemberNamingStrategy strategy; private final Map<DexMethod, DexString> renaming = new IdentityHashMap<>(); @@ -141,12 +140,11 @@ MethodNameMinifier( AppView<AppInfoWithLiveness> appView, - SubtypingInfo subtypingInfo, MemberNamingStrategy strategy) { this.appView = appView; - this.subtypingInfo = subtypingInfo; this.strategy = strategy; rootReservationState = MethodReservationState.createRoot(getReservationKeyTransform()); + reservationStates.put(null, rootReservationState); rootNamingState = MethodNamingState.createRoot(getNamingKeyTransform(), strategy, rootReservationState); namingStates.put(null, rootNamingState); @@ -202,7 +200,8 @@ timing.end(); // Phase 4: Assign names top-down by traversing the subtype hierarchy. timing.begin("Phase 4"); - assignNamesToClassesMethods(appView.dexItemFactory().objectType, rootNamingState); + assignNamesToClassesMethods(); + renameMethodsInUnrelatedClasspathClasses(); timing.end(); timing.begin("Phase 5: non-rebound references"); renameNonReboundReferences(executorService); @@ -211,21 +210,46 @@ return new MethodRenaming(renaming); } - private void assignNamesToClassesMethods(DexType type, MethodNamingState<?> parentNamingState) { - MethodReservationState<?> reservationState = - reservationStates.get(frontiers.getOrDefault(type, type)); - assert reservationState != null : "Could not find reservation state for " + type.toString(); - MethodNamingState<?> namingState = - namingStates.computeIfAbsent( - type, ignore -> parentNamingState.createChild(reservationState)); - DexClass holder = appView.definitionFor(type); - if (holder != null && strategy.allowMemberRenaming(holder)) { - for (DexEncodedMethod method : holder.allMethodsSorted()) { - assignNameToMethod(holder, method, namingState); - } - } - for (DexType subType : subtypingInfo.allImmediateExtendsSubtypes(type)) { - assignNamesToClassesMethods(subType, namingState); + private void assignNamesToClassesMethods() { + TopDownClassHierarchyTraversal.forAllClasses(appView) + .excludeInterfaces() + .visit( + appView.appInfo().classes(), + clazz -> { + DexType type = clazz.type; + MethodReservationState<?> reservationState = + reservationStates.get(frontiers.getOrDefault(type, type)); + assert reservationState != null + : "Could not find reservation state for " + type.toString(); + MethodNamingState<?> namingState = + namingStates.computeIfAbsent( + type, + ignore -> + namingStates + .getOrDefault(clazz.superType, rootNamingState) + .createChild(reservationState)); + DexClass holder = appView.definitionFor(type); + if (holder != null && strategy.allowMemberRenaming(holder)) { + for (DexEncodedMethod method : holder.allMethodsSorted()) { + assignNameToMethod(holder, method, namingState); + } + } + }); + } + + private void renameMethodsInUnrelatedClasspathClasses() { + if (appView.options().getProguardConfiguration().hasApplyMappingFile()) { + appView + .appInfo() + .forEachReferencedClasspathClass( + clazz -> { + for (DexEncodedMethod method : clazz.methods()) { + DexString reservedName = strategy.getReservedName(method, clazz); + if (reservedName != null && reservedName != method.getReference().name) { + renaming.put(method.getReference(), reservedName); + } + } + }); } } @@ -248,42 +272,30 @@ } private void reserveNamesInClasses() { - reserveNamesInClasses( - appView.dexItemFactory().objectType, - appView.dexItemFactory().objectType, - rootReservationState); + TopDownClassHierarchyTraversal.forAllClasses(appView) + .visit( + appView.appInfo().classes(), + clazz -> { + DexType type = clazz.type; + DexType frontier = frontiers.getOrDefault(clazz.superType, type); + if (frontier != type || clazz.isProgramClass()) { + DexType existingValue = frontiers.put(clazz.type, frontier); + assert existingValue == null; + } + // If this is not a program class (or effectively a library class as it is missing) + // move the frontier forward. This will ensure all reservations are put on the library + // or classpath frontier for the program path. + allocateReservationStateAndReserve( + type, + frontier, + reservationStates.getOrDefault(clazz.superType, rootReservationState)); + }); } - private void reserveNamesInClasses( - DexType type, DexType libraryFrontier, MethodReservationState<?> parentReservationState) { - assert !appView.isInterface(type).isTrue(); - - MethodReservationState<?> reservationState = - allocateReservationStateAndReserve(type, libraryFrontier, parentReservationState); - - // If this is not a program class (or effectively a library class as it is missing) move the - // frontier forward. This will ensure all reservations are put on the library or classpath - // frontier for the program path. - DexClass holder = appView.definitionFor(type); - for (DexType subtype : subtypingInfo.allImmediateExtendsSubtypes(type)) { - reserveNamesInClasses( - subtype, - holder == null || holder.isNotProgramClass() ? subtype : libraryFrontier, - reservationState); - } - } - - private MethodReservationState<?> allocateReservationStateAndReserve( + private void allocateReservationStateAndReserve( DexType type, DexType frontier, MethodReservationState<?> parent) { - assert parent != null; - - if (frontier != type) { - frontiers.put(type, frontier); - } - MethodReservationState<?> state = reservationStates.computeIfAbsent(frontier, ignore -> parent.createChild()); - DexClass holder = appView.definitionFor(type); if (holder != null) { for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) { @@ -293,8 +305,6 @@ } } } - - return state; } private MethodNamingState<?> getOrAllocateMethodNamingStates(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java index e7106a6..738be08 100644 --- a/src/main/java/com/android/tools/r8/naming/Minifier.java +++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -66,7 +66,7 @@ MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView); timing.begin("MinifyMethods"); MethodRenaming methodRenaming = - new MethodNameMinifier(appView, subtypingInfo, minifyMembers) + new MethodNameMinifier(appView, minifyMembers) .computeRenaming(interfaces, executorService, timing); timing.end();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java index 5c07e4a..7ee0681 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -147,7 +147,7 @@ new ApplyMappingMemberNamingStrategy(appView, memberNames); timing.begin("MinifyMethods"); MethodRenaming methodRenaming = - new MethodNameMinifier(appView, subtypingInfo, nameStrategy) + new MethodNameMinifier(appView, nameStrategy) .computeRenaming(interfaces, executorService, timing); // Amend the method renamings with the default interface methods. methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java index de68c75..32bc033 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -127,6 +127,7 @@ return resolvedMethod.isLibraryMethod() && isAccessibleInAllContexts(resolvedMethod, resolutionResult, contexts) && !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType) + && !isInvokeSuperToAbstractMethod(resolvedMethod, invokeType) && isApiSafeForMemberRebinding( resolvedMethod.asLibraryMethod(), androidApiLevelCompute, options); } @@ -147,6 +148,10 @@ return method.getHolder().isInterface() && invokeType.isSuper(); } + private boolean isInvokeSuperToAbstractMethod(DexClassAndMethod method, Type invokeType) { + return method.getAccessFlags().isAbstract() && invokeType.isSuper(); + } + public static DexField validMemberRebindingTargetFor( DexDefinitionSupplier definitions, DexClassAndField field, DexField original) { if (field.isProgramField()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java index 7c196b1..d8757d1 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -86,7 +86,8 @@ DexMethod methodReferenceBeforeParameterRemoval = method.getReference(); DexMethod methodReferenceAfterParameterRemoval = graphLens.internalGetNextMethodSignature(methodReferenceBeforeParameterRemoval); - if (methodReferenceAfterParameterRemoval == methodReferenceBeforeParameterRemoval) { + if (methodReferenceAfterParameterRemoval == methodReferenceBeforeParameterRemoval + && !graphLens.hasPrototypeChanges(methodReferenceAfterParameterRemoval)) { return method; } @@ -97,6 +98,13 @@ RewrittenPrototypeDescription prototypeChanges = graphLens.getPrototypeChanges(methodReferenceAfterParameterRemoval); builder.apply(prototypeChanges.createParameterAnnotationsRemover(method)); + + if (method.isInstance() + && prototypeChanges.getArgumentInfoCollection().isArgumentRemoved(0)) { + builder + .modifyAccessFlags(flags -> flags.demoteFromFinal().promoteToStatic()) + .unsetIsLibraryMethodOverride(); + } } }); });
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java index 17865e4..0955d0a 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.NestedGraphLens; import com.android.tools.r8.graph.RewrittenPrototypeDescription; +import com.android.tools.r8.ir.code.Invoke.Type; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; @@ -48,6 +49,11 @@ } @Override + protected boolean isLegitimateToHaveEmptyMappings() { + return true; + } + + @Override protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) { FieldLookupResult lookupResult = super.internalDescribeLookupField(previous); if (lookupResult.getReference().getType() != previous.getReference().getType()) { @@ -91,6 +97,17 @@ return super.internalGetNextMethodSignature(method); } + @Override + protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) { + if (!type.isStatic() && hasPrototypeChanges(newMethod)) { + RewrittenPrototypeDescription prototypeChanges = getPrototypeChanges(newMethod); + if (prototypeChanges.getArgumentInfoCollection().isArgumentRemoved(0)) { + return Type.STATIC; + } + } + return super.mapInvocationType(newMethod, originalMethod, type); + } + public static class Builder { private final AppView<AppInfoWithLiveness> appView; @@ -106,7 +123,9 @@ } public boolean isEmpty() { - return newFieldSignatures.isEmpty() && newMethodSignatures.isEmpty(); + return newFieldSignatures.isEmpty() + && newMethodSignatures.isEmpty() + && prototypeChanges.isEmpty(); } public ArgumentPropagatorGraphLens.Builder mergeDisjoint( @@ -136,6 +155,12 @@ return this; } + public Builder recordStaticized( + DexMethod method, RewrittenPrototypeDescription prototypeChangesForMethod) { + prototypeChanges.put(method, prototypeChangesForMethod); + return this; + } + public ArgumentPropagatorGraphLens build() { if (isEmpty()) { return null;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java index d79d23e..6479ad3 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -201,7 +201,8 @@ ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod(); DexMethod rewrittenMethodReference = graphLens.internalGetNextMethodSignature(resolvedMethod.getReference()); - if (rewrittenMethodReference != resolvedMethod.getReference()) { + if (rewrittenMethodReference != resolvedMethod.getReference() + || graphLens.hasPrototypeChanges(rewrittenMethodReference)) { markAffected(); } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java index 4890dd9..0e68d70 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -32,6 +32,7 @@ import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo; import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo; import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder; +import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.KeepFieldInfo; import com.android.tools.r8.utils.AccessUtils; @@ -345,7 +346,7 @@ // all possible to strengthen to the same stronger type, in all methods. Int2ReferenceMap<DexType> newParameterTypes = new Int2ReferenceOpenHashMap<>(); IntSet removableVirtualMethodParametersInAllMethods = new IntArraySet(); - for (int parameterIndex = 1; + for (int parameterIndex = 0; parameterIndex < signature.getProto().getArity() + 1; parameterIndex++) { if (canRemoveParameterFromVirtualMethods(methods, parameterIndex)) { @@ -455,6 +456,17 @@ private boolean canRemoveParameterFromVirtualMethods( ProgramMethodSet methods, int parameterIndex) { + if (parameterIndex == 0) { + if (methods.size() > 1) { + // Method staticizing would break dynamic dispatch. + return false; + } + ProgramMethod method = methods.getFirst(); + return method.getOptimizationInfo().hasUnusedArguments() + && method.getOptimizationInfo().getUnusedArguments().get(0) + && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method) + && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, parameterIndex); + } for (ProgramMethod method : methods) { if (method.getDefinition().isAbstract()) { // OK, this parameter can be removed. @@ -501,6 +513,9 @@ private DexType getNewParameterTypeForVirtualMethods( ProgramMethodSet methods, int parameterIndex) { + if (parameterIndex == 0) { + return null; + } DexType newParameterType = null; for (ProgramMethod method : methods) { if (method.getAccessFlags().isAbstract()) { @@ -548,6 +563,9 @@ partialGraphLensBuilder.recordMove( method.getReference(), newMethodSignature, prototypeChanges); affected.set(); + } else if (!prototypeChanges.isEmpty()) { + partialGraphLensBuilder.recordStaticized(method.getReference(), prototypeChanges); + affected.set(); } }); return affected.get(); @@ -907,42 +925,50 @@ ProgramMethod method, IntFunction<DexType> newParameterTypes, IntPredicate removableParameterIndices) { - ConcreteCallSiteOptimizationInfo optimizationInfo = - method.getOptimizationInfo().getArgumentInfos().asConcreteCallSiteOptimizationInfo(); - if (optimizationInfo == null) { - return ArgumentInfoCollection.empty(); + ArgumentInfoCollection.Builder parameterChangesBuilder = ArgumentInfoCollection.builder(); + if (method.getDefinition().isInstance() + && removableParameterIndices.test(0) + && method.getOptimizationInfo().hasUnusedArguments() + && method.getOptimizationInfo().getUnusedArguments().get(0) + && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method) + && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, 0)) { + parameterChangesBuilder.addArgumentInfo( + 0, RemovedArgumentInfo.builder().setType(method.getHolderType()).build()); } - ArgumentInfoCollection.Builder parameterChangesBuilder = ArgumentInfoCollection.builder(); - for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex(); - argumentIndex < method.getDefinition().getNumberOfArguments(); - argumentIndex++) { - if (removableParameterIndices.test(argumentIndex)) { - AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex); - if (abstractValue.isSingleValue() - && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) { + ConcreteCallSiteOptimizationInfo optimizationInfo = + method.getOptimizationInfo().getArgumentInfos().asConcreteCallSiteOptimizationInfo(); + if (optimizationInfo != null) { + for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex(); + argumentIndex < method.getDefinition().getNumberOfArguments(); + argumentIndex++) { + if (removableParameterIndices.test(argumentIndex)) { + AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex); + if (abstractValue.isSingleValue() + && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) { + parameterChangesBuilder.addArgumentInfo( + argumentIndex, + RemovedArgumentInfo.builder() + .setSingleValue(abstractValue.asSingleValue()) + .setType(method.getArgumentType(argumentIndex)) + .build()); + continue; + } + } + + DexType dynamicType = newParameterTypes.apply(argumentIndex); + if (dynamicType != null) { + DexType staticType = method.getArgumentType(argumentIndex); + assert dynamicType != staticType; parameterChangesBuilder.addArgumentInfo( argumentIndex, - RemovedArgumentInfo.builder() - .setSingleValue(abstractValue.asSingleValue()) - .setType(method.getArgumentType(argumentIndex)) + RewrittenTypeInfo.builder() + .setCastType(dynamicType) + .setOldType(staticType) + .setNewType(dynamicType) .build()); - continue; } } - - DexType dynamicType = newParameterTypes.apply(argumentIndex); - if (dynamicType != null) { - DexType staticType = method.getArgumentType(argumentIndex); - assert dynamicType != staticType; - parameterChangesBuilder.addArgumentInfo( - argumentIndex, - RewrittenTypeInfo.builder() - .setCastType(dynamicType) - .setOldType(staticType) - .setNewType(dynamicType) - .build()); - } } return parameterChangesBuilder.build(); }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java new file mode 100644 index 0000000..0d16b37 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
@@ -0,0 +1,45 @@ +// 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.optimize.argumentpropagation.utils; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.KeepMethodInfo; +import com.android.tools.r8.utils.InternalOptions; + +public class ParameterRemovalUtils { + + public static boolean canRemoveUnusedParametersFrom( + AppView<AppInfoWithLiveness> appView, ProgramMethod method) { + KeepMethodInfo keepInfo = appView.getKeepInfo(method); + InternalOptions options = appView.options(); + if (!keepInfo.isParameterRemovalAllowed(options)) { + return false; + } + return !appView.appInfoWithLiveness().isMethodTargetedByInvokeDynamic(method); + } + + public static boolean canRemoveUnusedParameter( + AppView<AppInfoWithLiveness> appView, ProgramMethod method, int argumentIndex) { + assert canRemoveUnusedParametersFrom(appView, method); + if (argumentIndex == 0) { + if (method.getDefinition().isInstanceInitializer()) { + return false; + } + if (method.getDefinition().isInstance()) { + if (method.getAccessFlags().isSynchronized()) { + return false; + } + KeepMethodInfo keepInfo = appView.getKeepInfo(method); + InternalOptions options = appView.options(); + if (!keepInfo.isMethodStaticizingAllowed(options)) { + return false; + } + } + } + return true; + } +}
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 2a5405b..68dba3e 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -53,6 +53,7 @@ import com.android.tools.r8.graph.DexReference; 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.DirectMappedDexApplication; import com.android.tools.r8.graph.DirectMappedDexApplication.Builder; import com.android.tools.r8.graph.EnclosingMethodAttribute; @@ -1009,6 +1010,14 @@ LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo(), context); if (descriptor == null) { + for (DexValue bootstrapArgument : callSite.getBootstrapArgs()) { + if (bootstrapArgument.isDexValueMethodHandle()) { + DexMethodHandle method = bootstrapArgument.asDexValueMethodHandle().getValue(); + if (method.isMethodHandle()) { + methodsTargetedByInvokeDynamic.add(method.asMethod()); + } + } + } return; }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java index 570ed16..62529ed 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -249,6 +249,12 @@ if (prunedItems.hasRemovedClasses()) { keepClassInfo.keySet().removeAll(prunedItems.getRemovedClasses()); } + if (prunedItems.hasRemovedFields()) { + keepFieldInfo.keySet().removeAll(prunedItems.getRemovedFields()); + } + if (prunedItems.hasRemovedMembers()) { + keepMethodInfo.keySet().removeAll(prunedItems.getRemovedMethods()); + } } public void removeKeepInfoForPrunedItems(PrunedItems prunedItems) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index c882cf4..a7362e5 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1335,7 +1335,8 @@ } List<FormalTypeParameter> formals = source.getClassSignature().getFormalTypeParameters(); if (genericArgumentsToSuperType.size() != formals.size()) { - assert false : "Invalid argument count to formals"; + // TODO(b/214509535): Correctly rewrite signature when type arguments is empty. + assert genericArgumentsToSuperType.isEmpty() : "Invalid argument count to formals"; return null; } Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java index 06fdc4c..13f630e 100644 --- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -47,39 +47,6 @@ return results; } - /** - * Filters the input array based on the given predicate. - * - * @param clazz target type's Class to cast - * @param original an array of original elements - * @param filter a predicate that tells us what to keep - * @param <T> target type - * @return a partial copy of the original array - */ - public static <T> T[] filter(Class<T[]> clazz, T[] original, Predicate<T> filter) { - ArrayList<T> filtered = null; - for (int i = 0; i < original.length; i++) { - T elt = original[i]; - if (filter.test(elt)) { - if (filtered != null) { - filtered.add(elt); - } - } else { - if (filtered == null) { - filtered = new ArrayList<>(original.length); - for (int j = 0; j < i; j++) { - filtered.add(original[j]); - } - } - } - } - if (filtered == null) { - return original; - } - return filtered.toArray( - clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size()))); - } - public static <T> boolean isEmpty(T[] array) { return array.length == 0; } @@ -131,8 +98,23 @@ return results != null ? results.toArray(emptyArray) : original; } - public static <T> T[] filter(T[] original, Predicate<T> test, T[] emptyArray) { - return map(original, e -> test.test(e) ? e : null, emptyArray); + public static <T> T[] filter(T[] original, Predicate<T> predicate, T[] emptyArray) { + return map(original, e -> predicate.test(e) ? e : null, emptyArray); + } + + @SuppressWarnings("unchecked") + public static <T> T[] filter(T[] original, Predicate<T> predicate, T[] emptyArray, int newSize) { + T[] result = (T[]) Array.newInstance(emptyArray.getClass().getComponentType(), newSize); + int newIndex = 0; + for (int originalIndex = 0; originalIndex < original.length; originalIndex++) { + T element = original[originalIndex]; + if (predicate.test(element)) { + result[newIndex] = element; + newIndex++; + } + } + assert newIndex == newSize; + return result; } public static int[] createIdentityArray(int size) {
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 f3dcf92..c1dc2c9 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -54,6 +54,7 @@ import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; import com.android.tools.r8.ir.desugar.nest.Nest; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.enums.EnumDataMap; @@ -74,7 +75,6 @@ import com.android.tools.r8.shaking.ProguardConfigurationRule; import com.android.tools.r8.utils.IROrdering.IdentityIROrdering; import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering; -import com.android.tools.r8.utils.collections.DexClassAndMethodSet; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.android.tools.r8.utils.structural.Ordered; import com.google.common.annotations.VisibleForTesting; @@ -1037,7 +1037,7 @@ DexType libraryType, DexType invalidSuperType, String message, - DexClassAndMethodSet retarget) { + Set<DexMethod> retarget) { if (invalidLibraryClasses.add(invalidSuperType)) { reporter.warning( new InvalidLibrarySuperclassDiagnostic( @@ -1046,8 +1046,7 @@ Reference.classFromDescriptor(invalidSuperType.toDescriptorString()), message, Lists.newArrayList( - Iterables.transform( - retarget, method -> method.getReference().asMethodReference())))); + Iterables.transform(retarget, method -> method.asMethodReference())))); } } @@ -1615,6 +1614,9 @@ public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {}; + // Meant to replace desugaredLibrarySpecification, set only from tests at the moment. + public MachineDesugaredLibrarySpecification machineDesugaredLibrarySpecification = null; + /** * If this flag is enabled, we will also compute the set of possible targets for invoke- * interface and invoke-virtual instructions that target a library method, and add the
diff --git a/src/test/examples/inlining/NoHorizontalClassMerging.java b/src/test/examples/inlining/NoHorizontalClassMerging.java new file mode 100644 index 0000000..fcd446d --- /dev/null +++ b/src/test/examples/inlining/NoHorizontalClassMerging.java
@@ -0,0 +1,10 @@ +// 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 inlining; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +public @interface NoHorizontalClassMerging {}
diff --git a/src/test/examples/inlining/NoMethodStaticizing.java b/src/test/examples/inlining/NoMethodStaticizing.java new file mode 100644 index 0000000..1ffa6f0 --- /dev/null +++ b/src/test/examples/inlining/NoMethodStaticizing.java
@@ -0,0 +1,10 @@ +// 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 inlining; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +public @interface NoMethodStaticizing {}
diff --git a/src/test/examples/inlining/Nullability.java b/src/test/examples/inlining/Nullability.java index 4c8dcb0..472e06a 100644 --- a/src/test/examples/inlining/Nullability.java +++ b/src/test/examples/inlining/Nullability.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package inlining; +@NoHorizontalClassMerging class Nullability { private final int f; public final int publicField; @@ -48,6 +49,7 @@ return a != null ? a.b() : -1; } + @NoMethodStaticizing int notInlinableOnThrow(Throwable t) throws Throwable { // NPE is not preserved if t is not a NullPointerException. throw t; @@ -61,6 +63,7 @@ } } + @NoMethodStaticizing public int notInlinableDueToMissingNpeBeforeThrow(Throwable t) throws Throwable { try { throw t;
diff --git a/src/test/examples/inlining/keep-rules.txt b/src/test/examples/inlining/keep-rules.txt index 2c0f288..bf9bc45 100644 --- a/src/test/examples/inlining/keep-rules.txt +++ b/src/test/examples/inlining/keep-rules.txt
@@ -17,3 +17,7 @@ -keepconstantarguments class * { @inlining.KeepConstantArguments <methods>; } +-nohorizontalclassmerging @inlining.NoHorizontalClassMerging class * +-nomethodstaticizing class * { + @inlining.NoMethodStaticizing <methods>; +}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java index 9f88bbb..420bf8d 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
@@ -11,6 +11,7 @@ import static junit.framework.TestCase.assertEquals; import static org.hamcrest.MatcherAssert.assertThat; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -50,6 +51,7 @@ .apply(ApiModelingTestHelper::enableApiCallerIdentification) // We are testing that we do not inline/merge higher api-levels .apply(ApiModelingTestHelper::disableOutliningAndStubbing) + .enableNoMethodStaticizingAnnotations() .noMinification() .compile() .inspect( @@ -94,6 +96,8 @@ } public interface ApiCaller { + + @NoMethodStaticizing default void callApiLevel() { System.out.println("ApiCaller::callApiLevel"); Api.apiLevel22();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java index da967e8..d105c17 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -54,6 +55,7 @@ .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .addVerticallyMergedClassesInspector( inspector -> { if (parameters.isDexRuntime() @@ -105,6 +107,7 @@ public static class Sub extends Base { @NeverInline + @NoMethodStaticizing public void callCallApi() { System.out.println("Sub::callCallApi"); callApi();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java index 7276937..352cd3a 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -54,6 +55,7 @@ .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .addVerticallyMergedClassesInspector( inspector -> { if (parameters.isDexRuntime() @@ -105,6 +107,7 @@ public static class Sub extends Base { @NeverInline + @NoMethodStaticizing public void callCallApi() { System.out.println("Sub::callCallApi"); callApi();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java index b9cf537..581fca7 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -91,7 +91,7 @@ }; } - static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> + public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> ThrowableConsumer<T> setMockApiLevelForClass(Class<?> clazz, AndroidApiLevel apiLevel) { return compilerBuilder -> { compilerBuilder.addOptionsModification( @@ -104,7 +104,8 @@ }; } - static void enableApiCallerIdentification(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { + public static void enableApiCallerIdentification( + TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { compilerBuilder.addOptionsModification( options -> { options.apiModelingOptions().enableApiCallerIdentification = true;
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java index 036dd46..fb4f27e 100644 --- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java +++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -501,6 +501,7 @@ } private void configure(InternalOptions options) { + options.callSiteOptimizationOptions().setEnableMethodStaticizing(false); // Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees // bothers what the tests want to check, such as exact instructions in the body that include // invocation kinds, like virtual call to a bridge.
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java index bf166ba..1eb5247 100644 --- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java +++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.KeepConstantArguments; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -75,6 +76,7 @@ .enableConstantArgumentAnnotations() .enableInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNeverClassInliningAnnotations() // TODO(b/173398086): uniqueMethodWithName() does not work with argument changes. @@ -125,6 +127,7 @@ // in TestClass.main(). @KeepConstantArguments @NeverInline + @NoMethodStaticizing /*bridge*/ String bridgeB(Object o) { return (String) m((String) o); } @@ -138,6 +141,7 @@ // access bridgeC() via class C. From B, the bridge can be hoisted again to A. @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String bridgeC(Object o) { return (String) m((String) o); } @@ -150,6 +154,7 @@ // in TestClass.main(). @KeepConstantArguments @NeverInline + @NoMethodStaticizing /*bridge*/ String bridgeB(Object o, int a, int b, int c, int d, int e) { return (String) m((String) o, a, b, c, d, e); } @@ -163,6 +168,7 @@ // access bridgeC() via class C. From B, the bridge can be hoisted again to A. @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String bridgeC(Object o, int a, int b, int c, int d, int e) { return (String) m((String) o, a, b, c, d, e); }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java index bdd55fc..120d7b4 100644 --- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java +++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -55,6 +56,7 @@ .enableConstantArgumentAnnotations() .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoHorizontalClassMergingAnnotations() // TODO(b/173398086): uniqueMethodWithName() does not work with argument changes. .noMinification() @@ -112,11 +114,13 @@ @KeepConstantArguments @NeverInline + @NoMethodStaticizing public Object m(String arg) { return System.currentTimeMillis() >= 0 ? arg : null; } @NeverInline + @NoMethodStaticizing public Object m2(String arg) { return System.currentTimeMillis() >= 0 ? arg : null; } @@ -129,6 +133,7 @@ // invoke-virtual instruction. @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String superBridge(Object o) { return (String) super.m((String) o); } @@ -136,6 +141,7 @@ // This bridge can be hoisted to A. @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String virtualBridge(Object o) { return (String) m((String) o); } @@ -148,6 +154,7 @@ // By hoisting B1.superBridge() to A this method bridge redundant. @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String superBridge(Object o) { return (String) super.m((String) o); } @@ -155,6 +162,7 @@ // By hoisting B1.virtualBridge() to A this method bridge redundant. @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String virtualBridge(Object o) { return (String) m((String) o); } @@ -182,12 +190,14 @@ @KeepConstantArguments @NeverInline + @NoMethodStaticizing public String superBridge(Object o) { return System.currentTimeMillis() >= 0 ? ((String) o) : null; } @KeepConstantArguments @NeverInline + @NoMethodStaticizing public String virtualBridge(Object o) { return System.currentTimeMillis() >= 0 ? ((String) o) : null; } @@ -201,12 +211,14 @@ @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String superBridge(Object o) { return (String) super.m2((String) o); } @KeepConstantArguments @NeverInline + @NoMethodStaticizing public /*bridge*/ String virtualBridge(Object o) { return (String) m2((String) o); }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java index 36cc8be..c26b1c6 100644 --- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java +++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.KeepConstantArguments; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest; import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest.CWithRangedInvoke; @@ -18,6 +19,7 @@ public static class A { @NeverInline + @NoMethodStaticizing public Object m(String arg) { return System.currentTimeMillis() > 0 ? arg : null; } @@ -37,6 +39,7 @@ @KeepConstantArguments @NeverInline + @NoMethodStaticizing public Object m(String arg, int a, int b, int c, int d, int e) { return System.currentTimeMillis() > 0 ? arg + a + b + c + d + e : null; }
diff --git a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java index 43d02cb..792f793 100644 --- a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java +++ b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.cf; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -52,6 +53,7 @@ .addProgramClasses(TestClass.class, A.class) .addKeepMainRule(TestClass.class) .addDontWarn(B.class) + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .addRunClasspathFiles(getRuntimeClasspath()) @@ -66,6 +68,7 @@ static class A { @NeverInline + @NoMethodStaticizing public void foo() { System.out.println("A::foo"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java index 48e6535..d276d0c 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
@@ -44,6 +44,8 @@ .addHorizontallyMergedClassesInspector( inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged()) + .addOptionsModification( + options -> options.callSiteOptimizationOptions().setEnableMethodStaticizing(false)) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableUnusedArgumentAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java index 508b10b..ef4dacd 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestParameters; import com.android.tools.r8.classmerging.horizontal.testclasses.InterfacesVisibilityTestClasses; import com.android.tools.r8.classmerging.horizontal.testclasses.InterfacesVisibilityTestClasses.ImplementingPackagePrivateInterface; @@ -32,6 +33,7 @@ .addKeepMainRule(Main.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoParameterTypeStrengtheningAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNoHorizontalClassMergingAnnotations() @@ -59,6 +61,7 @@ @NeverClassInline public static class A { @NeverInline + @NoMethodStaticizing public void bar() { System.out.println("bar"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java index 4ebbd51..621a805 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestParameters; import org.junit.Test; @@ -26,6 +27,7 @@ .addKeepMainRule(Main.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), Main.class) @@ -47,6 +49,7 @@ @NeverClassInline public static class B { @NeverInline + @NoMethodStaticizing public void bar() { System.out.println("bar"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java index f540d9d..d96c16c 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -48,6 +49,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() @@ -84,6 +86,7 @@ abstract static class A { @NeverInline + @NoMethodStaticizing void m() { System.out.println("A.m()"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java index 6529d20..440ccde 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -70,6 +71,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -110,6 +112,7 @@ @NoVerticalClassMerging interface J { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("J"); } @@ -120,6 +123,7 @@ @NoVerticalClassMerging interface K { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("K"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java index 5ee0365..dc48b45 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -60,6 +61,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -100,6 +102,7 @@ @NoVerticalClassMerging interface J { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("J"); } @@ -110,6 +113,7 @@ @NoVerticalClassMerging interface K { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("K"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java index a1dbbaf..9fa9260 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -55,6 +56,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -107,6 +109,7 @@ @NoVerticalClassMerging interface J { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("J"); } @@ -117,6 +120,7 @@ @NoVerticalClassMerging interface K { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("K"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java index 2c40a58..a544c7e 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -50,6 +51,7 @@ }) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -80,6 +82,7 @@ @NoVerticalClassMerging interface I { @NeverInline + @NoMethodStaticizing default void f() { System.out.println("I"); } @@ -89,6 +92,7 @@ @NoVerticalClassMerging interface J { @NeverInline + @NoMethodStaticizing default void g() { System.out.println("J"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java index e9cd1c1..f58252e 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -48,6 +49,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -96,6 +98,7 @@ // Intentionally package private. If J is merged into I then this is an illegal override of // I.m(). @NeverInline + @NoMethodStaticizing void m() { System.out.println("A.m()"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java index 00a1394..420ccaa 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -49,6 +50,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -101,6 +103,7 @@ // Intentionally package private. If J is merged into I then this is an illegal override of // I.m(). @NeverInline + @NoMethodStaticizing void m() { System.out.println("A.m()"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java index f68dd90..9d18660 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -48,6 +49,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -91,6 +93,7 @@ @NoVerticalClassMerging interface I { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("I"); } @@ -100,6 +103,7 @@ @NoVerticalClassMerging interface J { @NeverInline + @NoMethodStaticizing default void m() { System.out.println("J"); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsTest.java new file mode 100644 index 0000000..00d8557 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsTest.java
@@ -0,0 +1,60 @@ +// 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.classmerging.vertical; + +import com.android.tools.r8.NeverInline; +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; + +@RunWith(Parameterized.class) +public class VerticalClassMergingWithMissingTypeArgsTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8Compat(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*") + .addVerticallyMergedClassesInspector( + inspector -> inspector.assertMergedIntoSubtype(A.class)) + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("B.m()"); + } + + static class Main { + + public static void main(String[] args) { + new B().m(); + } + } + + static class A<T> {} + + interface I<T> {} + + static class B extends A implements I<B> { + @NeverInline + void m() { + System.out.println("B.m()"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java new file mode 100644 index 0000000..1e66d40 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java
@@ -0,0 +1,47 @@ +// 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.desugar.backports; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class AtomicReferenceFieldUpdaterTest extends AbstractBackportTest { + + @Parameters(name = "{0}") + public static Iterable<?> data() { + return getTestParameters() + .withDexRuntimesStartingFromExcluding(Version.V4_0_4) + .withAllApiLevels() + .build(); + } + + public AtomicReferenceFieldUpdaterTest(TestParameters parameters) { + super(parameters, AtomicReferenceFieldUpdater.class, Main.class); + + ignoreInvokes("newUpdater"); + + // java.util.concurrent.atomic.AtomicReferenceFieldUpdater issue is on API 31, see b/211646483. + registerTarget(AndroidApiLevel.Sv2, 3); + } + + public static class Main extends MiniAssert { + public volatile String field; + + public static void main(String[] args) throws Exception { + AtomicReferenceFieldUpdater<Main, String> updater = + AtomicReferenceFieldUpdater.newUpdater(Main.class, String.class, "field"); + Main x = new Main(); + assertTrue(updater.compareAndSet(x, null, "A")); + assertTrue(updater.compareAndSet(x, "A", "B")); + assertFalse(updater.compareAndSet(x, "A", "B")); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java index bd338b6..8db2a9c 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -5,14 +5,19 @@ package com.android.tools.r8.desugar.backports; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.CoreMatchers.not; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.Reference; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; @@ -20,13 +25,38 @@ import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.stream.Collectors; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +@RunWith(Parameterized.class) public class TestBackportedNotPresentInAndroidJar extends TestBase { + @Parameter(0) + public TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private Set<DexMethod> expectedToAlwaysBePresentInAndroidJar(DexItemFactory factory) + throws Exception { + MethodReference compareAndSet = + Reference.methodFromMethod( + AtomicReferenceFieldUpdater.class.getDeclaredMethod( + "compareAndSet", Object.class, Object.class, Object.class)); + assert compareAndSet.getReturnType().getTypeName().equals("boolean"); + return ImmutableSet.of(factory.createMethod(compareAndSet)); + } + @Test public void testBackportedMethodsPerAPILevel() throws Exception { for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) { @@ -43,6 +73,7 @@ List<DexMethod> backportedMethods = BackportedMethodRewriter.generateListOfBackportedMethods( AndroidApp.builder().build(), options, ThreadUtils.getExecutorService(options)); + Set<DexMethod> alwaysPresent = expectedToAlwaysBePresentInAndroidJar(options.itemFactory); for (DexMethod method : backportedMethods) { // Two different DexItemFactories are in play, but as toSourceString is used for lookup // that is not an issue. @@ -55,7 +86,9 @@ .map(DexType::toSourceString) .collect(Collectors.toList())); assertThat( - foundInAndroidJar + " present in " + apiLevel, foundInAndroidJar, not(isPresent())); + foundInAndroidJar + " present in " + apiLevel, + foundInAndroidJar, + notIf(isPresent(), !alwaysPresent.contains(method))); } } }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java index dbe597d..00134b5 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
@@ -39,7 +39,7 @@ ignoreInvokes("objectFieldOffset"); - // sun.misc.Unsafe issue is on API 31. + // sun.misc.Unsafe issue is on API 31, see b/211646483.. registerTarget(AndroidApiLevel.Sv2, 3); }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java index 61a1bf8..6bf49a0 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -5,7 +5,13 @@ package com.android.tools.r8.desugar.desugaredlibrary; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter; +import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.InternalOptions; +import java.io.IOException; import java.nio.file.Path; import java.time.Instant; import java.time.ZonedDateTime; @@ -25,15 +31,19 @@ private final TestParameters parameters; private final boolean shrinkDesugaredLibrary; + private final boolean machineSpec; - @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}") + @Parameters(name = "machine: {0}, {2}, shrink: {1}") public static List<Object[]> data() { return buildParameters( BooleanUtils.values(), + BooleanUtils.values(), getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); } - public RetargetOverrideTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + public RetargetOverrideTest( + boolean machineSpec, boolean shrinkDesugaredLibrary, TestParameters parameters) { + this.machineSpec = machineSpec; this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; this.parameters = parameters; } @@ -44,6 +54,7 @@ Path desugaredTwice = testForD8(Backend.CF) .addLibraryFiles(getLibraryFile()) + .addOptionsModification(this::setMachineSpec) .addProgramFiles( testForD8(Backend.CF) .addLibraryFiles(getLibraryFile()) @@ -65,6 +76,7 @@ stdout = testForD8(Backend.DEX) .addProgramFiles(desugaredTwice) + .addOptionsModification(this::setMachineSpec) .setMinApi(parameters.getApiLevel()) .disableDesugaring() .compile() @@ -95,6 +107,22 @@ assertLines2By2Correct(stdout); } + private void setMachineSpec(InternalOptions opt) { + if (!machineSpec) { + return; + } + try { + HumanDesugaredLibrarySpecification human = + new LegacyToHumanSpecificationConverter() + .convert(opt.desugaredLibrarySpecification, getLibraryFile(), opt); + MachineDesugaredLibrarySpecification machine = + new HumanToMachineSpecificationConverter().convert(human, getLibraryFile(), opt); + opt.testing.machineDesugaredLibrarySpecification = machine; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Test public void testRetargetOverrideD8() throws Exception { Assume.assumeTrue(parameters.getRuntime().isDex()); @@ -102,6 +130,7 @@ String stdout = testForD8() .addLibraryFiles(getLibraryFile()) + .addOptionsModification(this::setMachineSpec) .addInnerClasses(RetargetOverrideTest.class) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) .setMinApi(parameters.getApiLevel()) @@ -127,6 +156,7 @@ String stdout = testForR8(Backend.DEX) .addLibraryFiles(getLibraryFile()) + .addOptionsModification(this::setMachineSpec) .addKeepMainRule(Executor.class) .addInnerClasses(RetargetOverrideTest.class) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java index c531710..d510c33 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -44,6 +44,11 @@ .addKeepMainRule(TestClass.class) .addKeepRules(enumKeepRules.getKeepRules()) .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(UnboxableEnum.class)) + .addHorizontallyMergedClassesInspector( + inspector -> + inspector + .assertMergedInto(CompanionHost.class, Companion.class) + .assertNoOtherClassesMerged()) .enableNeverClassInliningAnnotations() .enableInliningAnnotations() .noMinification() // For assertions. @@ -65,10 +70,9 @@ isPresent()); return; } - MethodSubject method = - codeInspector.clazz(CompanionHost.class).uniqueMethodWithName(renamedMethodName); + MethodSubject method = codeInspector.clazz(Companion.class).uniqueMethodWithName("method"); assertThat(method, isPresent()); - assertEquals("int", method.getMethod().getReference().proto.parameters.toString()); + assertEquals("int", method.getMethod().getParameters().toString()); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java index 093f902..9dcd9cb 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.KeepConstantArguments; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -49,6 +50,7 @@ .enableConstantArgumentAnnotations() .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .addOptionsModification(options -> enableEnumOptions(options, enumValueOptimization)) .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class)) @@ -93,6 +95,7 @@ @KeepConstantArguments @NeverInline + @NoMethodStaticizing void m(int x, MyEnum y) { System.out.println("B.m(" + x + " : int, " + y.toString() + " : MyEnum)"); }
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java index 644fc9c..cb43c01 100644 --- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java +++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -6,6 +6,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertTrue; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -146,6 +147,7 @@ .setMinApi(parameters.getApiLevel()) .addKeepMainRule(MainClassFailing.class) .addOptionsModification(o -> o.testing.allowTypeErrors = true) + .enableNoMethodStaticizingAnnotations() .run(parameters.getRuntime(), MainClassFailing.class) .apply(r -> checkNonVerifyingResult(r, true)); } @@ -190,6 +192,7 @@ static class SubClassOfInvokerClass extends InvokerClass { + @NoMethodStaticizing public void subLevel2Method() { System.out.println("subLevel2Method in SubClassOfInvokerClass"); }
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java index 99ab7b3..d05122a 100644 --- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -50,6 +51,7 @@ .addProgramClasses(I.class) .addProgramClassFileData(getClassWithTransformedInvoked()) .addKeepMainRule(Main.class) + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), Main.class) .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class) @@ -75,6 +77,7 @@ public interface I { + @NoMethodStaticizing default void foo() { System.out.println("Hello World!"); }
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java index 6b593fd..128f780 100644 --- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java +++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -58,6 +58,7 @@ testForR8(Backend.CF) .addProgramFiles(outDirectory.resolve("program.jar")) .apply(this::configure) + .apply(this::configureCf) .compile(); testForR8(Backend.DEX) .addProgramFiles(compileResult.writeToZip()) @@ -79,6 +80,7 @@ testForR8Compat(Backend.CF) .addProgramFiles(outDirectory.resolve("program.jar")) .apply(this::configure) + .apply(this::configureCf) .compile(); testForR8Compat(Backend.DEX) .addProgramFiles(compileResult.writeToZip()) @@ -93,7 +95,12 @@ .addKeepRuleFiles(outDirectory.resolve("proguard.config")) .setMinApi(AndroidApiLevel.M) .allowDiagnosticMessages() + .allowUnnecessaryDontWarnWildcards() .allowUnusedDontWarnPatterns() .allowUnusedProguardConfigurationRules(); } + + private void configureCf(R8TestBuilder<?> testBuilder) { + testBuilder.addOptionsModification(options -> options.horizontalClassMergerOptions().disable()); + } }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java index 82bc605..2e3512d 100644 --- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java +++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -86,7 +86,7 @@ OptimizationFeedbackMock feedback = new OptimizationFeedbackMock(); FieldBitAccessAnalysis fieldBitAccessAnalysis = new FieldBitAccessAnalysis(); FieldAccessAnalysis fieldAccessAnalysis = - new FieldAccessAnalysis(appView, null, fieldBitAccessAnalysis, null); + new FieldAccessAnalysis(appView, null, fieldBitAccessAnalysis, null, null); DexProgramClass clazz = appView.appInfo().classes().iterator().next(); assertEquals(TestClass.class.getTypeName(), clazz.type.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java new file mode 100644 index 0000000..b3f658c --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java
@@ -0,0 +1,23 @@ +// 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.ir.desugar.backports; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +public final class AtomicReferenceFieldUpdaterMethods { + // Workaround Android S issue with AtomicReferenceFieldUpdater.compareAndSet (b/211646483). + public static boolean compareAndSet( + AtomicReferenceFieldUpdater<Object, Object> updater, + Object object, + Object expect, + Object update) { + do { + if (updater.compareAndSet(object, expect, update)) { + return true; + } + } while (updater.get(object) == expect); + return false; + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java index f955ca3..c6e3eea 100644 --- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -34,6 +34,7 @@ factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;"); private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of( + AtomicReferenceFieldUpdaterMethods.class, BooleanMethods.class, ByteMethods.class, CharSequenceMethods.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java index 62a44a1..bded17f 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -363,7 +363,7 @@ assertThat(clazz.uniqueMethodWithName("conditionalOperator"), isAbsent()); // The enum parameter is unboxed. - MethodSubject m = clazz.uniqueMethodWithName("moreControlFlows$enumunboxing$"); + MethodSubject m = clazz.uniqueMethodWithName("moreControlFlows"); assertTrue(m.isPresent()); // Verify that a.b() is resolved to an inline instance-get.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java index 5fd1825..e4b5197 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
@@ -8,12 +8,14 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; @@ -34,6 +36,7 @@ .withCfRuntimes() // java.util.function.Predicate is not available prior to API level 24 (V7.0). .withDexRuntimesStartingFromIncluding(Version.V7_0_0) + .withApiLevelsStartingAtIncluding(AndroidApiLevel.N) .build(); } @@ -44,10 +47,10 @@ public void testR8() throws Exception { R8TestCompileResult libraryCompileResult = testForR8(parameters.getBackend()) - .addProgramClasses(LibClass.class) - .addKeepClassAndMembersRules(LibClass.class) - .setMinApi(parameters.getRuntime()) - .compile(); + .addProgramClasses(LibClass.class) + .addKeepClassAndMembersRules(LibClass.class) + .setMinApi(parameters.getApiLevel()) + .compile(); testForR8(parameters.getBackend()) .addProgramClasses(TestClass.class, CustomPredicate.class) .addClasspathClasses(LibClass.class) @@ -56,7 +59,8 @@ o -> o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect) .enableInliningAnnotations() - .setMinApi(parameters.getRuntime()) + .enableNoMethodStaticizingAnnotations() + .setMinApi(parameters.getApiLevel()) .compile() .addRunClasspathFiles(libraryCompileResult.writeToZip()) .run(parameters.getRuntime(), MAIN) @@ -107,11 +111,13 @@ } @NeverInline + @NoMethodStaticizing private void live() { System.out.println("Live"); } @NeverInline + @NoMethodStaticizing private void alsoLive() { System.out.println("Also live"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java index 07df10b..3dc868d 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -4,8 +4,7 @@ package com.android.tools.r8.ir.optimize.callsites; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static junit.framework.TestCase.assertTrue; -import static org.hamcrest.CoreMatchers.not; +import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverClassInline; @@ -16,7 +15,6 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; -import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +41,11 @@ testForR8(parameters.getBackend()) .addInnerClasses(WithStaticizerTest.class) .addKeepMainRule(MAIN) + .addHorizontallyMergedClassesInspector( + inspector -> + inspector + .assertMergedInto(Host.class, Host.Companion.class) + .assertNoOtherClassesMerged()) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() @@ -55,14 +58,10 @@ private void inspect(CodeInspector inspector) { // Check if the candidate is indeed staticized. ClassSubject companion = inspector.clazz(Host.Companion.class); - assertThat(companion, not(isPresent())); - - // Null check in Companion#foo is migrated to Host#foo. - ClassSubject host = inspector.clazz(Host.class); - assertThat(host, isPresent()); - MethodSubject foo = host.uniqueMethodWithName("foo"); + assertThat(companion, isPresent()); + MethodSubject foo = companion.uniqueMethodWithName("foo"); assertThat(foo, isPresent()); - assertTrue(foo.streamInstructions().noneMatch(InstructionSubject::isIf)); + assertThat(foo, isStatic()); } @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java index 0cd06dc..ef373cd 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoParameterTypeStrengthening; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -44,6 +45,7 @@ testForR8(parameters.getBackend()) .addInnerClasses(InvokeDirectPositiveTest.class) .addKeepMainRule(MAIN) + .enableNoMethodStaticizingAnnotations() .enableNoParameterTypeStrengtheningAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNeverClassInliningAnnotations() @@ -109,6 +111,7 @@ } @NeverInline + @NoMethodStaticizing @NoParameterTypeStrengthening private void test(Base arg) { if (arg instanceof Sub1) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java index 0372989..3a3a62d 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -42,6 +43,7 @@ .addKeepMainRule(MAIN) .enableNeverClassInliningAnnotations() .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .addOptionsModification( o -> o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect) @@ -74,6 +76,7 @@ } @NeverInline + @NoMethodStaticizing private void test(Object arg) { if (arg != null) { System.out.println("non-null");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java index 6ea5d98..1884e5e 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -87,6 +87,7 @@ .addProgramClasses(classes) .enableInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .enableSideEffectAnnotations() .addKeepMainRule(main) .addKeepAttributes("LineNumberTable")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java index 0390712..f56779e 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.ir.optimize.classinliner.trivial; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; @NoHorizontalClassMerging public class CycleReferenceAB { @@ -14,6 +15,7 @@ this.a = a; } + @NoMethodStaticizing public void foo(int depth) { CycleReferenceBA ba = new CycleReferenceBA("depth=" + depth); System.out.println("CycleReferenceAB::foo(" + depth + ")");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java index 1c5d147..6d22b16 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.ir.optimize.classinliner.trivial; +import com.android.tools.r8.NoMethodStaticizing; + public class CycleReferenceBA { private String a; @@ -11,6 +13,7 @@ this.a = a; } + @NoMethodStaticizing public void foo(int depth) { CycleReferenceAB ab = new CycleReferenceAB("depth=" + depth); System.out.println("CycleReferenceBA::foo(" + depth + ")");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java index 86cf25e..820a5de 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.AssumeMayHaveSideEffects; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; public class TrivialTestClass { private static int ID = 0; @@ -88,6 +89,7 @@ } @NeverInline + @NoMethodStaticizing private void testCycles() { new CycleReferenceAB("first").foo(3); new CycleReferenceBA("second").foo(4);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java index 3d48fb9..b21bcab 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -42,6 +43,7 @@ .addInnerClasses(InvokeSuperToInvokeVirtualTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNeverClassInliningAnnotations() .setMinApi(parameters.getApiLevel()) @@ -98,6 +100,7 @@ } @NeverInline + @NoMethodStaticizing void world() { System.out.println(" world!"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java index f3db3a7..ca2d49a 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
@@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoReturnTypeStrengthening; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -44,6 +45,7 @@ // Keep B to ensure that we will treat it as being instantiated. .addKeepClassRulesWithAllowObfuscation(B.class) .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoReturnTypeStrengtheningAnnotations() .setMinApi(parameters.getApiLevel()) .compile() @@ -155,6 +157,7 @@ static class A implements I { @NeverInline + @NoMethodStaticizing @Override public void hello() { System.out.print("Hello");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java index 3a0b304..883c62a 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.KeepConstantArguments; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -66,6 +67,7 @@ .addInnerClasses(B134462736.class) .addKeepMainRule(TestClass.class) .enableConstantArgumentAnnotations() + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .noMinification() .addOptionsModification( @@ -83,6 +85,7 @@ @KeepConstantArguments @NeverInline + @NoMethodStaticizing public void consumer(String arg1, String arg2) { System.out.println(arg1 + " " + arg2); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java index f3a61c4..5987d3c 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.optimize.staticizer; +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -168,9 +169,9 @@ // instantiation of SimpleWithParams, it is marked as ineligible for staticizing. assertEquals( Lists.newArrayList( + "STATIC: String SimpleWithParams.bar(String)", "STATIC: String TrivialTestClass.next()", "SimpleWithParams SimpleWithParams.INSTANCE", - "VIRTUAL: String SimpleWithParams.bar(String)", "VIRTUAL: String SimpleWithParams.foo()"), references(clazz, "testSimpleWithParams", "void")); @@ -204,9 +205,9 @@ assertEquals( Lists.newArrayList( + "STATIC: String SimpleWithThrowingGetter.bar(String)", "STATIC: String TrivialTestClass.next()", "SimpleWithThrowingGetter SimpleWithThrowingGetter.INSTANCE", - "VIRTUAL: String SimpleWithThrowingGetter.bar(String)", "VIRTUAL: String SimpleWithThrowingGetter.foo()"), references(clazz, "testSimpleWithThrowingGetter", "void")); @@ -219,6 +220,7 @@ Lists.newArrayList( "DIRECT: void SimpleWithLazyInit.<init>()", "DIRECT: void SimpleWithLazyInit.<init>()", + "STATIC: String SimpleWithLazyInit.bar(String)", "STATIC: String TrivialTestClass.next()", "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE", "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE", @@ -226,7 +228,6 @@ "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE", "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE", "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE", - "VIRTUAL: String SimpleWithLazyInit.bar(String)", "VIRTUAL: String SimpleWithLazyInit.foo()"), references(clazz, "testSimpleWithLazyInit", "void")); @@ -300,35 +301,37 @@ assertEquals( Lists.newArrayList( - "STATIC: String movetohost.HostOk.bar(String)", - "STATIC: String movetohost.HostOk.foo()", + "STATIC: String movetohost.CandidateOk.bar(String)", + "STATIC: String movetohost.CandidateOk.foo()", "STATIC: String movetohost.MoveToHostTestClass.next()", "STATIC: String movetohost.MoveToHostTestClass.next()", - "STATIC: void movetohost.HostOk.blah(String)"), + "STATIC: void movetohost.CandidateOk.blah(String)"), references(clazz, "testOk", "void")); - assertThat(inspector.clazz(CandidateOk.class), not(isPresent())); + assertThat(inspector.clazz(HostOk.class), isAbsent()); + assertThat(inspector.clazz(CandidateOk.class), isPresent()); assertEquals( Lists.newArrayList( - "STATIC: String movetohost.HostOkSideEffects.bar(String)", - "STATIC: String movetohost.HostOkSideEffects.foo()", + "STATIC: String movetohost.CandidateOkSideEffects.bar(String)", + "STATIC: String movetohost.CandidateOkSideEffects.foo()", "STATIC: String movetohost.MoveToHostTestClass.next()", - "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"), + "movetohost.CandidateOkSideEffects movetohost.HostOkSideEffects.INSTANCE"), references(clazz, "testOkSideEffects", "void")); - assertThat(inspector.clazz(CandidateOkSideEffects.class), not(isPresent())); + assertThat(inspector.clazz(HostOkSideEffects.class), isPresent()); + assertThat(inspector.clazz(CandidateOkSideEffects.class), isPresent()); assertEquals( Lists.newArrayList( - "DIRECT: void movetohost.HostConflictMethod.<init>()", "STATIC: String movetohost.CandidateConflictMethod.bar(String)", "STATIC: String movetohost.CandidateConflictMethod.foo()", + "STATIC: String movetohost.HostConflictMethod.bar(String)", "STATIC: String movetohost.MoveToHostTestClass.next()", - "STATIC: String movetohost.MoveToHostTestClass.next()", - "VIRTUAL: String movetohost.HostConflictMethod.bar(String)"), + "STATIC: String movetohost.MoveToHostTestClass.next()"), references(clazz, "testConflictMethod", "void")); + assertThat(inspector.clazz(HostConflictMethod.class), isPresent()); assertThat(inspector.clazz(CandidateConflictMethod.class), isPresent()); assertEquals( @@ -427,12 +430,14 @@ assertThat(clazz.uniqueMethodWithName("calledTwice"), not(isPresent())); // Check that the two inlines of "calledTwice" is correctly rewritten. - assertThat(clazz.uniqueMethodWithName("foo"), isPresent()); - assertThat(clazz.uniqueMethodWithName("bar"), isPresent()); + ClassSubject candidateClassSubject = inspector.clazz(Candidate.class); + assertThat(candidateClassSubject, isPresent()); + assertThat(candidateClassSubject.uniqueMethodWithName("foo"), isPresent()); + assertThat(candidateClassSubject.uniqueMethodWithName("bar"), isPresent()); assertEquals( Lists.newArrayList( - "STATIC: String dualcallinline.DualCallTest.foo()", - "STATIC: String dualcallinline.DualCallTest.foo()"), + "STATIC: String dualcallinline.Candidate.foo()", + "STATIC: String dualcallinline.Candidate.foo()"), references(clazz, "main", "void", "java.lang.String[]")); } }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java index 74dc3f4..76bee19 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java
@@ -1,7 +1,7 @@ package com.android.tools.r8.ir.optimize.staticizer; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.CoreMatchers.not; +import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverInline; @@ -10,6 +10,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -43,18 +44,16 @@ } private void inspect(CodeInspector inspector) { - if (parameters.isCfRuntime()) { - // Class staticizer is disabled when generating class files. - assertThat(inspector.clazz(Companion.class), isPresent()); - } else { - // The companion class has been removed. - assertThat(inspector.clazz(Companion.class), not(isPresent())); + ClassSubject hostClassSubject = inspector.clazz(CompanionHost.class); + assertThat(hostClassSubject, isPresent()); - // The companion method has been moved to the companion host class. - ClassSubject hostClassSubject = inspector.clazz(CompanionHost.class); - assertThat(hostClassSubject, isPresent()); - assertThat(hostClassSubject.uniqueMethodWithName("method"), isPresent()); - } + // The companion class has been removed. + ClassSubject companionClassSubject = inspector.clazz(Companion.class); + assertThat(companionClassSubject, isPresent()); + + MethodSubject companionMethodSubject = companionClassSubject.uniqueMethodWithName("method"); + assertThat(companionMethodSubject, isPresent()); + assertThat(companionMethodSubject, isStatic()); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java index 58dc4e5..2c8e8ed 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.BooleanUtils; @@ -64,6 +65,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() .minification(minification) .allowAccessModification(allowAccessModification) .setMinApi(parameters.getApiLevel()) @@ -113,11 +115,13 @@ @NeverClassInline static class A { @NeverInline + @NoMethodStaticizing private void foo(B instantiated) { System.out.println("A#foo(" + instantiated + ")"); } @NeverInline + @NoMethodStaticizing void foo(B instantiated, C uninstantiated) { System.out.println("A#foo(" + instantiated + ", Object)"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java index 472ec24..f8dd8d9 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -10,9 +10,11 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -20,20 +22,18 @@ 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 VoidReturnTypeRewritingTest extends TestBase { - private final Backend backend; + @Parameter(0) + public TestParameters parameters; - @Parameters(name = "Backend: {0}") - public static Backend[] data() { - return ToolHelper.getBackends(); - } - - public VoidReturnTypeRewritingTest(Backend backend) { - this.backend = backend; + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); } @Test @@ -48,15 +48,17 @@ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected); CodeInspector inspector = - testForR8(backend) + testForR8(parameters.getBackend()) .addInnerClasses(VoidReturnTypeRewritingTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNoHorizontalClassMergingAnnotations() .addKeepRules("-dontobfuscate") .addOptionsModification(options -> options.enableClassInlining = false) - .run(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expected) .inspector(); @@ -116,6 +118,7 @@ } @NeverInline + @NoMethodStaticizing public Uninstantiated createVirtual() { System.out.print("Factory.createVirtual()"); return null; @@ -127,6 +130,7 @@ @Override @NeverInline + @NoMethodStaticizing public Uninstantiated createVirtual() { System.out.print("SubFactory.createVirtual()"); return null; @@ -138,6 +142,7 @@ @Override @NeverInline + @NoMethodStaticizing public SubUninstantiated createVirtual() { System.out.print("SubSubFactory.createVirtual()"); return null;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java index e990830..9cdc693 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.BooleanUtils; @@ -50,6 +51,7 @@ .addKeepMainRule(TestClass.class) .enableMemberValuePropagationAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableInliningAnnotations() .minification(minification) .setMinApi(parameters.getApiLevel()) @@ -87,6 +89,7 @@ @NeverInline @NeverPropagateValue + @NoMethodStaticizing public String toString(Object unused) { System.out.print("Hello "); return "world!";
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java index ed2d01d..abc1bd6 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.KeepConstantArguments; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.BooleanUtils; @@ -35,7 +36,7 @@ @Parameters(name = "{0}, minification: {1}, allowaccessmodification: {2}") public static List<Object[]> data() { return buildParameters( - getTestParameters().withAllRuntimes().build(), + getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values(), BooleanUtils.values()); } @@ -64,9 +65,10 @@ .enableConstantArgumentAnnotations() .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .minification(minification) .allowAccessModification(allowAccessModification) - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::verifyUnusedArgumentsRemovedAndNoCollisions) .run(parameters.getRuntime(), TestClass.class) @@ -113,12 +115,14 @@ static class A { @KeepConstantArguments @NeverInline + @NoMethodStaticizing private void foo(String used) { System.out.println("A#foo(" + used + ")"); } @KeepConstantArguments @NeverInline + @NoMethodStaticizing void foo(String used, Object unused) { System.out.println("A#foo(" + used + ", Object)"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java index c59eb85..7527598 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -59,6 +60,7 @@ .addKeepMainRule(TestClass.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .minification(minification) .setMinApi(parameters.getApiLevel()) @@ -119,6 +121,7 @@ static class A { @NeverInline + @NoMethodStaticizing public void method1() { System.out.print("Hello"); } @@ -138,6 +141,7 @@ } @NeverInline + @NoMethodStaticizing public void method2(Object unused) { System.out.println(" world"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java index c06823f..5497d76 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.R8FullTestBuilder; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.codeinspector.ClassSubject; @@ -58,31 +59,37 @@ } @NeverInline + @NoMethodStaticizing private Object privateMethod(Object a) { return a; } @NeverInline + @NoMethodStaticizing private Object privateMethod(Object a, Object b) { return a; } @NeverInline + @NoMethodStaticizing private Object privateMethod(Object a, Object b, Object c) { return a; } @NeverInline + @NoMethodStaticizing public Object publicMethod(Object a) { return a; } @NeverInline + @NoMethodStaticizing public Object publicMethod(Object a, Object b) { return a; } @NeverInline + @NoMethodStaticizing public Object publicMethod(Object a, Object b, Object c) { return a; } @@ -106,7 +113,10 @@ @Override public void configure(R8FullTestBuilder builder) { - builder.enableNeverClassInliningAnnotations().enableInliningAnnotations(); + builder + .enableNeverClassInliningAnnotations() + .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations(); } @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverInUnboxedEnumTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverInUnboxedEnumTest.java new file mode 100644 index 0000000..0218614 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverInUnboxedEnumTest.java
@@ -0,0 +1,57 @@ +// 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.ir.optimize.unusedarguments; + +import com.android.tools.r8.NeverInline; +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; + +@RunWith(Parameterized.class) +public class UnusedReceiverInUnboxedEnumTest 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) + .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class)) + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(NullPointerException.class); + } + + static class Main { + + public static void main(String[] args) { + MyEnum alwaysNull = System.currentTimeMillis() > 0 ? null : MyEnum.A; + alwaysNull.foo(); + } + } + + enum MyEnum { + A; + + @NeverInline + void foo() { + System.out.println("Hello!"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverTest.java new file mode 100644 index 0000000..175b2cb --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverTest.java
@@ -0,0 +1,65 @@ +// 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.ir.optimize.unusedarguments; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +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 UnusedReceiverTest 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) + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + ClassSubject mainClassSubject = inspector.clazz(Main.class); + assertThat(mainClassSubject, isPresent()); + + MethodSubject testMethodSubject = mainClassSubject.uniqueMethodWithName("test"); + assertThat(testMethodSubject, isStatic()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + static class Main { + + public static void main(String[] args) { + new Main().test(); + } + + @NeverInline + void test() { + System.out.println("Hello world!"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java index f3db67c..b05928d 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -19,10 +19,13 @@ import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.Move; +import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -65,6 +68,16 @@ } @Override + public InvokeMethod insertNullCheckInstruction( + AppView<?> appView, + IRCode code, + BasicBlockIterator blockIterator, + Value value, + Position position) { + throw new Unimplemented(); + } + + @Override public boolean replaceCurrentInstructionByNullCheckIfPossible( AppView<?> appView, ProgramMethod context) { throw new Unimplemented();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java index fea8bfa..ee3a60f 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -49,6 +50,7 @@ .addInnerClasses(getClass()) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .addKeepClassRules(Dependency.class) .addMainDexRules( @@ -101,6 +103,7 @@ // Will be rewritten because it has an unused argument @NeverInline + @NoMethodStaticizing public void foo(Object obj, int argumentUnused) { B.foo(obj); }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java new file mode 100644 index 0000000..24c31c2 --- /dev/null +++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
@@ -0,0 +1,108 @@ +// 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.memberrebinding; + +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.apimodel.ApiModelingTestHelper; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.codeinspector.CodeMatchers; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.google.common.collect.ImmutableList; +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; + +// This is a reproduction of b/213581039. +@RunWith(Parameterized.class) +public class MemberRebindingInvokeSuperAbstractTest extends TestBase { + + @Parameter() public TestParameters parameters; + + private final List<Class<?>> libraryClasses = + ImmutableList.of(LibraryBase.class, LibrarySub.class, LibrarySubSub.class); + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addLibraryClasses(libraryClasses) + .addDefaultRuntimeLibrary(parameters) + .addProgramClasses(Main.class) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) + .apply( + builder -> + libraryClasses.forEach(clazz -> setMockApiLevelForClass(clazz, AndroidApiLevel.B))) + .apply( + setMockApiLevelForMethod( + LibraryBase.class.getDeclaredMethod("getSystemService"), AndroidApiLevel.B)) + .apply( + setMockApiLevelForMethod( + LibrarySub.class.getDeclaredMethod("getSystemService"), AndroidApiLevel.B)) + .compile() + .addRunClasspathClasses(libraryClasses) + .inspect( + inspector -> { + MethodSubject getSystemService = + inspector.clazz(Main.class).uniqueMethodWithName("getSystemService"); + assertThat(getSystemService, isPresent()); + // We should never rebind this call to LibraryBase::getSystemService since this can + // cause errors when verifying the code on a device where the image has a definition + // but it is abstract. For more information, see b/213581039. + assertThat( + getSystemService, + CodeMatchers.invokesMethodWithHolderAndName( + typeName(LibrarySub.class), "getSystemService")); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("LibrarySub::getSystemService"); + } + + public abstract static class LibraryBase { + + public abstract void getSystemService(); + } + + public static class LibrarySub extends LibraryBase { + + @Override + public void getSystemService() { + System.out.println("LibrarySub::getSystemService"); + } + } + + public static class LibrarySubSub extends LibrarySub {} + + public static class Main extends LibrarySubSub { + + public static void main(String[] args) { + new Main().getSystemService(); + } + + @Override + @NeverInline + public void getSystemService() { + super.getSystemService(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringReprocessingTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringReprocessingTest.java new file mode 100644 index 0000000..4041e32 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringReprocessingTest.java
@@ -0,0 +1,72 @@ +// 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.naming; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverPropagateValue; +import com.android.tools.r8.ReprocessClassInitializer; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +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 IdentifierNameStringReprocessingTest 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) + .addKeepClassRulesWithAllowObfuscation(A.class) + .addKeepRules( + "-identifiernamestring class " + Main.class.getTypeName() + " {", + " static java.lang.String f;", + "}") + .enableMemberValuePropagationAnnotations() + .enableReprocessClassInitializerAnnotations() + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .apply( + runResult -> { + ClassSubject aClassSubject = runResult.inspector().clazz(A.class); + assertThat(aClassSubject, isPresentAndRenamed()); + runResult.assertSuccessWithOutputLines(aClassSubject.getFinalName()); + }); + } + + @ReprocessClassInitializer + static class Main { + + @NeverPropagateValue static String f; + + static { + // Prevent class initializer defaults optimization. + System.out.print(""); + f = "com.android.tools.r8.naming.IdentifierNameStringReprocessingTest$A"; + } + + public static void main(String[] args) { + System.out.println(f); + } + } + + static class A {} +}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java index 670b6ae..99554bf 100644 --- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java +++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -4,18 +4,24 @@ package com.android.tools.r8.naming.applymapping; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestBase; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import org.junit.Assume; 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 ApplyMappingAfterDevirtualizationTest extends TestBase { @@ -35,19 +41,22 @@ public static class LibClassA implements LibInterfaceA { @Override + @NoMethodStaticizing public void foo() { System.out.println("LibClassA::foo"); } } - // LibClassB should be devirtualized into LibInterfaceB + // LibInterfaceB should be devirtualized into LibClassB public static class LibClassB implements LibInterfaceB { @Override + @NoMethodStaticizing public void foo() { System.out.println("LibClassB::foo"); } + @NoMethodStaticizing public void bar() { System.out.println("LibClassB::bar"); } @@ -71,92 +80,94 @@ } } - private static final Class<?>[] LIBRARY_CLASSES = { + private static final Class<?>[] CLASSPATH_CLASSES = { LibInterfaceA.class, LibInterfaceB.class, LibClassA.class, LibClassB.class }; private static final Class<?>[] PROGRAM_CLASSES = {ProgramClass.class}; - private Backend backend; + @Parameter(0) + public TestParameters parameters; - @Parameterized.Parameters(name = "{0}") - public static Backend[] data() { - return ToolHelper.getBackends(); - } - - public ApplyMappingAfterDevirtualizationTest(Backend backend) { - this.backend = backend; + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); } @Test public void runOnJvm() throws Throwable { - Assume.assumeTrue(backend == Backend.CF); + Assume.assumeTrue(parameters.isCfRuntime()); testForJvm() - .addProgramClasses(LIBRARY_CLASSES) + .addProgramClasses(CLASSPATH_CLASSES) .addProgramClasses(PROGRAM_CLASSES) - .run(ProgramClass.class) + .run(parameters.getRuntime(), ProgramClass.class) .assertSuccessWithOutput(EXPECTED_OUTPUT); } @Test public void devirtualizingNoRenamingOfOverriddenNotKeptInterfaceMethods() throws Exception { R8TestCompileResult libraryResult = - testForR8(backend) - .addProgramClasses(LIBRARY_CLASSES) + testForR8(parameters.getBackend()) + .addProgramClasses(CLASSPATH_CLASSES) .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class) .addKeepMainRule(LibClassB.class) + .addKeepClassAndDefaultConstructor(LibClassB.class) .addOptionsModification(options -> options.inlinerOptions().enableInlining = false) + .enableNoMethodStaticizingAnnotations() + .setMinApi(parameters.getApiLevel()) .compile(); CodeInspector inspector = libraryResult.inspector(); - assertThat(inspector.clazz(LibClassA.class), isPresent()); - assertThat(inspector.clazz(LibClassB.class), isPresent()); + assertThat(inspector.clazz(LibClassA.class), isPresentAndRenamed()); + assertThat(inspector.clazz(LibClassB.class), isPresentAndNotRenamed()); // LibInterfaceX should have been moved into LibClassX. assertThat(inspector.clazz(LibInterfaceA.class), not(isPresent())); assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent())); - testForR8(backend) + testForR8(parameters.getBackend()) .noTreeShaking() .noMinification() .addProgramClasses(PROGRAM_CLASSES) .addApplyMapping(libraryResult.getProguardMap()) - .addLibraryClasses(LIBRARY_CLASSES) - .addLibraryFiles(runtimeJar(backend)) + .addClasspathClasses(CLASSPATH_CLASSES) + .setMinApi(parameters.getApiLevel()) .compile() .addRunClasspathFiles(libraryResult.writeToZip()) - .run(ProgramClass.class) + .run(parameters.getRuntime(), ProgramClass.class) .assertSuccessWithOutput(EXPECTED_OUTPUT); } @Test public void devirtualizingNoRenamingOfOverriddenKeptInterfaceMethods() throws Exception { R8TestCompileResult libraryResult = - testForR8(backend) - .addProgramClasses(LIBRARY_CLASSES) - .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class, LibInterfaceA.class) - .addKeepMainRule(LibClassB.class) + testForR8(parameters.getBackend()) + .addProgramClasses(CLASSPATH_CLASSES) + .addKeepClassAndMembersRulesWithAllowObfuscation( + LibClassA.class, LibClassB.class, LibInterfaceA.class) .addOptionsModification(options -> options.inlinerOptions().enableInlining = false) + .enableNoMethodStaticizingAnnotations() + .setMinApi(parameters.getApiLevel()) .compile(); CodeInspector inspector = libraryResult.inspector(); - assertThat(inspector.clazz(LibClassA.class), isPresent()); - assertThat(inspector.clazz(LibClassB.class), isPresent()); + assertThat(inspector.clazz(LibClassA.class), isPresentAndRenamed()); + assertThat(inspector.clazz(LibClassB.class), isPresentAndRenamed()); // LibInterfaceA is now kept. - assertThat(inspector.clazz(LibInterfaceA.class), isPresent()); + assertThat(inspector.clazz(LibInterfaceA.class), isPresentAndRenamed()); assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent())); - testForR8(backend) + testForR8(parameters.getBackend()) .noTreeShaking() .noMinification() .addProgramClasses(PROGRAM_CLASSES) .addApplyMapping(libraryResult.getProguardMap()) - .addLibraryClasses(LIBRARY_CLASSES) - .addLibraryFiles(runtimeJar(backend)) + .addClasspathClasses(CLASSPATH_CLASSES) + .setMinApi(parameters.getApiLevel()) .compile() .addRunClasspathFiles(libraryResult.writeToZip()) - .run(ProgramClass.class) + .run(parameters.getRuntime(), ProgramClass.class) .assertSuccessWithOutput(EXPECTED_OUTPUT); } }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java index a1ef87f..b4b6807 100644 --- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java +++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java
@@ -4,13 +4,10 @@ package com.android.tools.r8.naming.applymapping; -import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; -import java.io.IOException; -import java.util.concurrent.ExecutionException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -57,7 +54,7 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } public ApplyMappingInterfaceInvokeTest(TestParameters parameters) { @@ -65,14 +62,13 @@ } @Test - public void testInvokeVirtual() - throws IOException, CompilationFailedException, ExecutionException { + public void testInvokeVirtual() throws Exception { Class<?>[] classPathClasses = {I.class, A.class, B.class, C.class}; R8TestCompileResult libraryResult = testForR8(parameters.getBackend()) .addProgramClasses(classPathClasses) .addKeepAllClassesRuleWithAllowObfuscation() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile(); testForR8(parameters.getBackend()) .addClasspathClasses(classPathClasses) @@ -80,7 +76,7 @@ .noMinification() .noTreeShaking() .addApplyMapping(libraryResult.getProguardMap()) - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() .addRunClasspathFiles(libraryResult.writeToZip()) .run(parameters.getRuntime(), TestApp.class)
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java new file mode 100644 index 0000000..b5cd101 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
@@ -0,0 +1,95 @@ +// 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.naming.retrace; + +import static com.android.tools.r8.naming.retrace.StackTrace.isSame; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; +import com.android.tools.r8.SingleTestRunResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Before; +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 RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + public StackTrace expectedStackTrace; + public StackTrace unexpectedStackTrace; + + @Before + public void setup() throws Exception { + expectedStackTrace = getStackTrace(); + unexpectedStackTrace = getStackTrace("foo"); + } + + private StackTrace getStackTrace(String... args) throws Exception { + SingleTestRunResult<?> runResult = + testForRuntime(parameters) + .addProgramClasses(Caller.class, Foo.class) + .run(parameters.getRuntime(), Caller.class, args); + return parameters.isCfRuntime() + ? runResult.map(StackTrace::extractFromJvm) + : StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm()); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Caller.class) + .addKeepAttributeLineNumberTable() + .addKeepAttributeSourceFile() + .enableExperimentalMapFileVersion() + .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Caller.class) + .assertFailureWithErrorThatThrows(NullPointerException.class) + // TODO(b/214377135): Should retrace to expectedStackTrace. + .inspectStackTrace( + (stackTrace, codeInspector) -> assertThat(stackTrace, isSame(unexpectedStackTrace))); + } + + static class Foo { + + @NeverInline + @NoMethodStaticizing + void checkNull() { + System.out.println("Hello, world!"); + } + + void inlinable(Foo foo) { + checkNull(); + foo.checkNull(); + } + } + + static class Caller { + + @NeverInline + static void caller(Foo f) { + f.inlinable(null); + } + + public static void main(String[] args) { + caller(args.length == 0 ? new Foo() : null); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java index 7c0f956..bf8856b 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java
@@ -4,13 +4,16 @@ package com.android.tools.r8.naming.retrace; import static com.android.tools.r8.naming.retrace.StackTrace.isSame; +import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverInline; import com.android.tools.r8.SingleTestRunResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; import java.util.List; +import java.util.Objects; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -61,7 +64,27 @@ .assertFailureWithErrorThatThrows(NullPointerException.class) .inspectStackTrace( (stackTrace, codeInspector) -> { - assertThat(stackTrace, isSame(expectedStackTrace)); + if (canUseJavaUtilObjectsRequireNonNull(parameters)) { + StackTrace requireNonNullFrame = + StackTrace.builder().add(stackTrace.get(0)).build(); + assertThat( + requireNonNullFrame, + isSameExceptForLineNumbers( + StackTrace.builder() + .add( + StackTraceLine.builder() + .setClassName(Objects.class.getTypeName()) + .setMethodName("requireNonNull") + .setFileName("Objects.java") + .build()) + .build())); + + StackTrace stackTraceWithoutRequireNonNull = + StackTrace.builder().add(stackTrace).remove(0).build(); + assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace)); + } else { + assertThat(stackTrace, isSame(expectedStackTrace)); + } }); }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java index 81c2200..13e3e7f 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.naming.retrace; import static com.android.tools.r8.naming.retrace.StackTrace.isSame; +import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverInline; @@ -13,8 +14,10 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.TestBuilder; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; import com.android.tools.r8.utils.BooleanUtils; import java.util.List; +import java.util.Objects; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -80,7 +83,27 @@ .assertFailureWithErrorThatThrows(NullPointerException.class) .inspectStackTrace( (stackTrace, codeInspector) -> { - assertThat(stackTrace, isSame(expectedStackTrace)); + if (throwReceiverNpe && canUseJavaUtilObjectsRequireNonNull(parameters)) { + StackTrace requireNonNullFrame = + StackTrace.builder().add(stackTrace.get(0)).build(); + assertThat( + requireNonNullFrame, + isSameExceptForLineNumbers( + StackTrace.builder() + .add( + StackTraceLine.builder() + .setClassName(Objects.class.getTypeName()) + .setMethodName("requireNonNull") + .setFileName("Objects.java") + .build()) + .build())); + + StackTrace stackTraceWithoutRequireNonNull = + StackTrace.builder().add(stackTrace).remove(0).build(); + assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace)); + } else { + assertThat(stackTrace, isSame(expectedStackTrace)); + } }); }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java index aea2a25..f4158a1 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -76,6 +76,11 @@ return this; } + public Builder remove(int i) { + stackTraceLines.remove(i); + return this; + } + public Builder applyIf(boolean condition, Consumer<Builder> fn) { if (condition) { fn.accept(this);
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java index a0c58ac..a8f51d2 100644 --- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -41,6 +42,7 @@ .addKeepMainRule(Main.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .inspect( @@ -77,6 +79,7 @@ static class A { @NeverInline + @NoMethodStaticizing public String toString(String whichOne) { return System.currentTimeMillis() > 0 ? whichOne : null; }
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java new file mode 100644 index 0000000..f5fb4f5 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java
@@ -0,0 +1,87 @@ +// 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.optimize.argumentpropagation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic; +import static junit.framework.TestCase.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +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 CompanionConstructorShakingTest 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.enableClassStaticizer = false) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + ClassSubject hostClassSubject = inspector.clazz(Host.class); + assertThat(hostClassSubject, isAbsent()); + + ClassSubject companionClassSubject = inspector.clazz(Host.Companion.class); + assertThat(companionClassSubject, isPresent()); + assertEquals(1, companionClassSubject.allMethods().size()); + + MethodSubject greetMethodSubject = + companionClassSubject.uniqueMethodWithName("greet"); + assertThat(greetMethodSubject, isStatic()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + static class Main { + + public static void main(String[] args) { + Host.companion.greet(); + } + } + + static class Host { + + static final Companion companion = new Companion(); + + @NeverClassInline + @NoHorizontalClassMerging + static class Companion { + + @NeverInline + void greet() { + System.out.println("Hello world!"); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java index b8f5492..115b180 100644 --- a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java +++ b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -39,6 +40,7 @@ .addKeepClassAndMembersRules(Main.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .inspect( @@ -63,6 +65,7 @@ // Should be made final. @NeverInline + @NoMethodStaticizing public void m() { System.out.println("A.m()"); }
diff --git a/src/test/java/com/android/tools/r8/regress/Regress214340258.java b/src/test/java/com/android/tools/r8/regress/Regress214340258.java new file mode 100644 index 0000000..bce5a5b --- /dev/null +++ b/src/test/java/com/android/tools/r8/regress/Regress214340258.java
@@ -0,0 +1,105 @@ +// 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.regress; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRuntime.CfRuntime; +import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.utils.codeinspector.FoundClassSubject; +import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class Regress214340258 extends TestBase { + // Generate this many classes to not overflow instruction limit. + static final int NUMBER_OF_FILES = 50; + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + public Regress214340258(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testR8() throws Exception { + Path compiledJumbo = getZipWithJumboString(); + R8TestRunResult r8TestRunResult = + testForR8(parameters.getBackend()) + .addDontOptimize() + .addKeepAllClassesRule() + .addProgramFiles(compiledJumbo) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), "TestClass0"); + r8TestRunResult.assertSuccessWithOutputLines("foobar"); + assertTrue(hasJumboString(r8TestRunResult)); + } + + private boolean hasJumboString(R8TestRunResult r8TestRunResult) + throws IOException, ExecutionException { + for (FoundClassSubject classSubject : r8TestRunResult.inspector().allClasses()) { + for (FoundMethodSubject foundMethodSubject : classSubject.allMethods()) { + for (InstructionSubject instruction : foundMethodSubject.instructions()) { + if (instruction.isJumboString()) { + return true; + } + } + } + } + return false; + } + + public Path getZipWithJumboString() throws IOException { + List<Path> javaFiles = new ArrayList<>(); + for (int i = 0; i < NUMBER_OF_FILES; i++) { + String name = "TestClass" + i; + Path file = temp.newFile(name + ".java").toPath(); + Files.write(file, getClassWithManyStrings(name, i).getBytes(StandardCharsets.UTF_8)); + javaFiles.add(file); + } + Path compiledJumbo = javac(CfRuntime.getCheckedInJdk9()).addSourceFiles(javaFiles).compile(); + return compiledJumbo; + } + + private String getClassWithManyStrings(String className, int index) { + String file = + "" + + "public class " + + className + + " {\n" + + " public static void use(String s) { }\n" + + "\n" + + " public static void main(String[] args) {\n" + + " String s = \"foobar\";\n"; + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < Constants.MAX_NON_JUMBO_INDEX / NUMBER_OF_FILES; i++) { + builder.append(" s = \"foobar" + i + "_" + index + "\";\n"); + builder.append(" System.getenv(s);\n"); + } + file += builder.toString(); + + file += "" + " s = \"foobar\";\n" + " System.out.println(s);\n" + " }\n" + "}"; + return file; + } +}
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java index 01d1c30..9a81a6a 100644 --- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java +++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -6,6 +6,7 @@ import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -92,13 +93,10 @@ .inspector(); List<FoundClassSubject> classes = inspector.allClasses(); - - // The synthetic class is still present when generating class files. - assertEquals(parameters.isCfRuntime() ? 3 : 2, classes.size()); - assertEquals( - parameters.isCfRuntime(), + assertEquals(2, classes.size()); + assertTrue( classes.stream() .map(FoundClassSubject::getOriginalName) - .anyMatch(name -> name.endsWith("$1"))); + .noneMatch(name -> name.endsWith("$1"))); } }
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java index 760215a..cbbfa70 100644 --- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java +++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -7,6 +7,7 @@ import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.assertEquals; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestRunResult; @@ -64,7 +65,7 @@ public void testResolutionAccess() throws Exception { AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness( - buildClasses(getClasses()) + buildClassesWithTestingAnnotations(getClasses()) .addClassProgramData(getTransformedClasses()) .addLibraryFile(parameters.getDefaultRuntimeLibrary()) .build(), @@ -91,6 +92,7 @@ testForR8(parameters.getBackend()) .addProgramClasses(getClasses()) .addProgramClassFileData(getTransformedClasses()) + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .addKeepMainRule(Main.class) .run(parameters.getRuntime(), Main.class) @@ -114,12 +116,14 @@ return transformer(clazz).setNest(clazz); } + @NoHorizontalClassMerging static class A { /* will be private */ static void bar() { System.out.println("A::bar"); } } + @NoHorizontalClassMerging static class B { public void foo() { // Static invoke to private method.
diff --git a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java index 24d4436..cb750b5 100644 --- a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java +++ b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -19,27 +20,26 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; @RunWith(Parameterized.class) public class EventuallyNonTargetedMethodTest extends TestBase { static final String EXPECTED = StringUtils.lines("A::foo", "C::bar"); - private final TestParameters parameters; + @Parameter(0) + public TestParameters parameters; @Parameterized.Parameters(name = "{0}") public static TestParametersCollection data() { return getTestParameters().withAllRuntimesAndApiLevels().build(); } - public EventuallyNonTargetedMethodTest(TestParameters parameters) { - this.parameters = parameters; - } - @Test public void test() throws Exception { testForR8(parameters.getBackend()) .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNoHorizontalClassMergingAnnotations() .enableNeverClassInliningAnnotations() @@ -78,11 +78,13 @@ // Non-targeted override. @Override + @NoMethodStaticizing public void foo() { System.out.println("C::foo"); } @NeverInline + @NoMethodStaticizing public void bar() { System.out.println("C::bar"); }
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java index 7c9c834..163a63f 100644 --- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java +++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -8,16 +8,15 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; import com.android.tools.r8.ByteDataView; import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfRuntime; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.DexVm.Version; -import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.ir.optimize.Inliner.Reason; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; @@ -93,50 +92,31 @@ memoizeFunction(NonVirtualOverrideTest::compile); public static String getExpectedResult(boolean isOldVm) throws Exception { - if (isOldVm) { - return String.join( - System.lineSeparator(), - "In A.m1()", - "In A.m2()", - "In A.m3()", - "In A.m4()", - "In C.m1()", - "In A.m2()", - "In C.m3()", - "In A.m4()", - "In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1() - "Caught IncompatibleClassChangeError when calling B.m3()", - "In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1() - "Caught IncompatibleClassChangeError when calling B.m3()", - "In C.m1()", - "In C.m3()", - ""); - } else { - Path referenceJar = staticTemp.getRoot().toPath().resolve("input.jar"); - ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar); - inputConsumer.accept( - ByteDataView.of(NonVirtualOverrideTestClassDump.dump()), - DescriptorUtils.javaTypeToDescriptor(NonVirtualOverrideTestClass.class.getName()), - null); - inputConsumer.accept( - ByteDataView.of(ADump.dump()), - DescriptorUtils.javaTypeToDescriptor(A.class.getName()), - null); - inputConsumer.accept( - ByteDataView.of(BDump.dump()), - DescriptorUtils.javaTypeToDescriptor(B.class.getName()), - null); - inputConsumer.accept( - ByteDataView.of(CDump.dump()), - DescriptorUtils.javaTypeToDescriptor(C.class.getName()), - null); - inputConsumer.finished(null); + Path referenceJar = staticTemp.getRoot().toPath().resolve("input.jar"); + ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar); + inputConsumer.accept( + ByteDataView.of(NonVirtualOverrideTestClassDump.dump()), + DescriptorUtils.javaTypeToDescriptor(NonVirtualOverrideTestClass.class.getName()), + null); + inputConsumer.accept( + ByteDataView.of(ADump.dump()), + DescriptorUtils.javaTypeToDescriptor(A.class.getName()), + null); + inputConsumer.accept( + ByteDataView.of(BDump.dump()), + DescriptorUtils.javaTypeToDescriptor(B.class.getName()), + null); + inputConsumer.accept( + ByteDataView.of(CDump.dump()), + DescriptorUtils.javaTypeToDescriptor(C.class.getName()), + null); + inputConsumer.finished(null); - ProcessResult javaResult = - ToolHelper.runJava(referenceJar, NonVirtualOverrideTestClass.class.getName()); - assertEquals(javaResult.exitCode, 0); - return javaResult.stdout; - } + return testForJvm(getStaticTemp()) + .addProgramFiles(referenceJar) + .run(CfRuntime.getDefaultCfRuntime(), NonVirtualOverrideTestClass.class) + .assertSuccess() + .getStdOut(); } public static boolean isDexVmBetween5_1_1and7_0_0(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java index 51bd0cf..dda29d7 100644 --- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.DataEntryResource; import com.android.tools.r8.NeverPropagateValue; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -92,6 +93,7 @@ }) .enableGraphInspector() .enableMemberValuePropagationAnnotations() + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput); @@ -189,6 +191,7 @@ new DataResourceConsumerForTesting(options.dataResourceConsumer); options.dataResourceConsumer = dataResourceConsumer; }) + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), OtherTestClass.class) .assertSuccessWithOutput(expectedOutput) @@ -230,6 +233,7 @@ public static class HelloGreeter implements Greeter { @NeverPropagateValue + @NoMethodStaticizing @Override public String greeting() { return "Hello"; @@ -238,6 +242,7 @@ public static class WorldGreeter implements Greeter { + @NoMethodStaticizing @Override public String greeting() { return " world!";
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java index 3f68961..dcb962b 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
@@ -8,6 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -43,6 +44,7 @@ .addInnerClasses(InterfaceInitializedByImplementationTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() .setMinApi(parameters.getApiLevel()) .noMinification() .compile() @@ -81,6 +83,7 @@ // TODO(b/144266257): If tree shaking removes this method, then I.<clinit>() won't be run when // A is being class initialized. @NeverInline + @NoMethodStaticizing default void m() { System.out.println(" world!"); }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java index 6518cfe..882076c 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
@@ -12,32 +12,32 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoMethodStaticizing; import com.android.tools.r8.TestBase; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.codeinspector.CodeInspector; 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 IfRuleWithAccessRelaxationTest extends TestBase { - private final Backend backend; + @Parameter(0) + public TestParameters parameters; - public IfRuleWithAccessRelaxationTest(Backend backend) { - this.backend = backend; - } - - @Parameters(name = "Backend: {0}") - public static Backend[] data() { - return ToolHelper.getBackends(); + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); } @Test public void r8Test() throws Exception { CodeInspector inspector = - testForR8(backend) + testForR8(parameters.getBackend()) .addInnerClasses(IfRuleWithAccessRelaxationTest.class) .addKeepMainRule(TestClass.class) .addKeepRules( @@ -54,6 +54,8 @@ "-keep class " + Unused3.class.getTypeName()) .allowAccessModification() .enableInliningAnnotations() + .enableNoMethodStaticizingAnnotations() + .setMinApi(parameters.getApiLevel()) .compile() .inspector(); @@ -80,6 +82,7 @@ protected int field = 42; @NeverInline + @NoMethodStaticizing private void privateMethod() { System.out.println("In privateMethod()"); }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java index b4e4fce..8c67d3e 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
@@ -4,15 +4,17 @@ package com.android.tools.r8.shaking.ifrule.classstaticizer; +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.shaking.ifrule.classstaticizer.IfRuleWithClassStaticizerTest.StaticizerCandidate.Companion; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -22,69 +24,67 @@ 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 IfRuleWithClassStaticizerTest extends TestBase { - private final Backend backend; + @Parameter(0) + public TestParameters parameters; - @Parameters(name = "Backend: {0}") - public static Backend[] data() { - return ToolHelper.getBackends(); - } - - public IfRuleWithClassStaticizerTest(Backend backend) { - this.backend = backend; + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); } @Test public void test() throws Exception { String expectedOutput = StringUtils.lines("In method()"); - if (backend == Backend.CF) { - testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput); + if (parameters.isCfRuntime()) { + testForJvm() + .addTestClasspath() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(expectedOutput); } CodeInspector inspector = - testForR8(backend) + testForR8(parameters.getBackend()) .addInnerClasses(IfRuleWithClassStaticizerTest.class) .addKeepMainRule(TestClass.class) .addKeepRules( - "-if class " + StaticizerCandidate.Companion.class.getTypeName() + " {", + "-if class " + Companion.class.getTypeName() + " {", " public !static void method();", "}", "-keep class " + Unused.class.getTypeName()) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() - .run(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput) .inspector(); ClassSubject classSubject = inspector.clazz(StaticizerCandidate.class); - assertThat(classSubject, isPresent()); + assertThat(classSubject, isAbsent()); - if (backend == Backend.CF) { + if (parameters.isCfRuntime()) { // The class staticizer is not enabled for CF. assertThat(inspector.clazz(Unused.class), isPresent()); } else { - assert backend == Backend.DEX; + assert parameters.isDexRuntime(); - // There should be a static method on StaticizerCandidate after staticizing. + // There should be a static method after staticizing. + ClassSubject companionClassSubject = inspector.clazz(StaticizerCandidate.Companion.class); + assertThat(companionClassSubject, isPresent()); List<FoundMethodSubject> staticMethods = - classSubject.allMethods().stream() + companionClassSubject.allMethods().stream() .filter(method -> method.isStatic() && !method.isClassInitializer()) .collect(Collectors.toList()); assertEquals(1, staticMethods.size()); - assertEquals( - "void " + StaticizerCandidate.Companion.class.getTypeName() + ".method()", - staticMethods.get(0).getOriginalSignature().toString()); + assertEquals("void method()", staticMethods.get(0).getOriginalSignature().toString()); - // The Companion class should not be present after staticizing. - assertThat(inspector.clazz(StaticizerCandidate.Companion.class), not(isPresent())); - - // TODO(b/122867080): The Unused class should be present due to the -if rule. - assertThat(inspector.clazz(Unused.class), not(isPresent())); + assertThat(inspector.clazz(Unused.class), isPresent()); } }
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java index b97fd41..93cc98f 100644 --- a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java +++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -113,7 +113,7 @@ public void testFilter_identity() { int size = 3; Integer[] input = createInputData(size); - Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> true); + Integer[] output = ArrayUtils.filter(input, x -> true, new Integer[0]); assertEquals(input, output); } @@ -121,7 +121,7 @@ public void testFilter_dropOdd() { int size = 3; Integer[] input = createInputData(size); - Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> x % 2 == 0); + Integer[] output = ArrayUtils.filter(input, x -> x % 2 == 0, new Integer[0]); assertNotEquals(input, output); assertEquals(2, output.length); assertEquals(0, (int) output[0]); @@ -132,7 +132,7 @@ public void testFilter_dropAll() { int size = 3; Integer[] input = createInputData(size); - Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> false); + Integer[] output = ArrayUtils.filter(input, x -> false, new Integer[0]); assertNotEquals(input, output); assertEquals(0, output.length); }