Merge commit '5c7d3ce0754f28f499a6073a83d2abde5cf7bc02' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 41ea658..d96d546 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -650,7 +650,7 @@ appView.protoShrinker().enumLiteProtoShrinker.verifyDeadEnumLiteMapsAreDead(); } - IRConverter converter = new IRConverter(appView, timing); + IRConverter converter = new IRConverter(appView); // If proto shrinking is enabled, we need to reprocess every dynamicMethod(). This ensures // that proto fields that have been removed by the second round of tree shaking are also @@ -1017,7 +1017,7 @@ static void processWhyAreYouKeepingAndCheckDiscarded( RootSet rootSet, - Supplier<Iterable<DexProgramClass>> classes, + Supplier<Collection<DexProgramClass>> classes, WhyAreYouKeepingConsumer whyAreYouKeepingConsumer, AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer,
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java index 261497d..8bbfcfd 100644 --- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java +++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -24,10 +24,12 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.synthesis.CommittedItems; import com.android.tools.r8.synthesis.SyntheticItems; +import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import java.util.Arrays; +import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -58,7 +60,16 @@ public void run(ExecutorService executorService) throws ExecutionException { if (appView.options().isGeneratingDex() && appView.options().apiModelingOptions().enableStubbingOfClasses) { - ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService); + Collection<DexProgramClass> classes = + ListUtils.filter( + appView.appInfo().classes(), DexProgramClass::originatesFromClassResource); + // Finding super types is really fast so no need to pay the overhead of threading if the + // number of classes is low. + if (classes.size() > 2) { + ThreadUtils.processItems(classes, this::processClass, executorService); + } else { + classes.forEach(this::processClass); + } } if (!libraryClassesToMock.isEmpty()) { libraryClassesToMock.forEach( @@ -98,6 +109,7 @@ } public void processClass(DexProgramClass clazz) { + assert clazz.originatesFromClassResource(); if (isAlreadyOutlined(clazz)) { return; } @@ -124,7 +136,7 @@ } private void findReferencedLibraryClasses(DexType type, DexProgramClass context) { - if (!type.isClassType()) { + if (!type.isClassType() || isJavaType(type)) { return; } WorkList<DexType> workList = WorkList.newIdentityWorkList(type, seenTypes); @@ -147,17 +159,18 @@ } } + private boolean isJavaType(DexType type) { + return type == appView.dexItemFactory().objectType + || type.getDescriptor().startsWith(appView.dexItemFactory().javaDescriptorPrefix); + } + private void mockMissingLibraryClass( DexLibraryClass libraryClass, ThrowExceptionCode throwExceptionCode, ApiReferenceStubberEventConsumer eventConsumer) { DexItemFactory factory = appView.dexItemFactory(); // Do not stub the anything starting with java (including the object type). - if (libraryClass.getType() == appView.dexItemFactory().objectType - || libraryClass - .getType() - .getDescriptor() - .startsWith(appView.dexItemFactory().javaDescriptorPrefix)) { + if (isJavaType(libraryClass.getType())) { return; } // Check if desugared library will bridge the type.
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java index 6c379bb..9b9b2b1 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -46,17 +46,21 @@ private final ConstantDynamicReference reference; public CfConstDynamic( + int symbolicReferenceId, DexString name, DexType type, DexMethodHandle bootstrapMethod, List<DexValue> bootstrapMethodArguments) { + assert symbolicReferenceId >= 0; assert name != null; assert type != null; assert bootstrapMethod != null; assert bootstrapMethodArguments != null; assert bootstrapMethodArguments.isEmpty(); - reference = new ConstantDynamicReference(name, type, bootstrapMethod, bootstrapMethodArguments); + reference = + new ConstantDynamicReference( + symbolicReferenceId, name, type, bootstrapMethod, bootstrapMethodArguments); } @Override @@ -86,7 +90,10 @@ } public static CfConstDynamic fromAsmConstantDynamic( - ConstantDynamic insn, JarApplicationReader application, DexType clazz) { + int symbolicReferenceId, + ConstantDynamic insn, + JarApplicationReader application, + DexType clazz) { String constantName = insn.getName(); String constantDescriptor = insn.getDescriptor(); DexMethodHandle bootstrapMethodHandle = @@ -99,6 +106,7 @@ bootstrapMethodArguments.add(dexValue); } return new CfConstDynamic( + symbolicReferenceId, application.getString(constantName), application.getTypeFromDescriptor(constantDescriptor), bootstrapMethodHandle,
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java index 72908ca..6e9c218 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
@@ -57,7 +57,7 @@ private StartupInstrumentation(AppView<AppInfo> appView) { this.appView = appView; - this.converter = new IRConverter(appView, Timing.empty()); + this.converter = new IRConverter(appView); this.dexItemFactory = appView.dexItemFactory(); this.options = appView.options(); this.references = new StartupInstrumentationReferences(dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java index 03a4ce6..53ebcee 100644 --- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java +++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -174,7 +174,7 @@ } private void writeIR(ProgramMethod method) { - IRConverter converter = new IRConverter(appInfo, timing); + IRConverter converter = new IRConverter(appInfo); MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty(); OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java index 45fed00..153f85f 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -79,6 +79,8 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -86,6 +88,7 @@ import java.util.List; import java.util.Map; import java.util.function.BiFunction; +import java.util.function.Supplier; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ConstantDynamic; @@ -334,6 +337,7 @@ private boolean usrJsrInliner; private final Origin origin; private final DebugParsingOptions debugParsingOptions; + private Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences; ClassCodeVisitor( DexClass clazz, @@ -351,6 +355,13 @@ this.debugParsingOptions = debugParsingOptions; } + private Reference2IntMap<ConstantDynamic> getConstantDynamicSymbolicReferences() { + if (constantDynamicSymbolicReferences == null) { + constantDynamicSymbolicReferences = new Reference2IntArrayMap<>(); + } + return constantDynamicSymbolicReferences; + } + @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { @@ -360,7 +371,13 @@ if (code != null) { DexMethod method = application.getMethod(clazz.type, name, desc); MethodCodeVisitor methodVisitor = - new MethodCodeVisitor(application, method, code, origin, debugParsingOptions); + new MethodCodeVisitor( + application, + method, + code, + origin, + debugParsingOptions, + this::getConstantDynamicSymbolicReferences); if (!usrJsrInliner) { return methodVisitor; } @@ -391,13 +408,16 @@ private final Origin origin; private int minLine = Integer.MAX_VALUE; private int maxLine = -1; + private final Supplier<Reference2IntMap<ConstantDynamic>> + constantDynamicSymbolicReferencesSupplier; MethodCodeVisitor( JarApplicationReader application, DexMethod method, LazyCfCode code, Origin origin, - DebugParsingOptions debugParsingOptions) { + DebugParsingOptions debugParsingOptions, + Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) { super(InternalOptions.ASM_VERSION); this.debugParsingOptions = debugParsingOptions; assert code != null; @@ -406,6 +426,7 @@ this.code = code; this.method = method; this.origin = origin; + this.constantDynamicSymbolicReferencesSupplier = constantDynamicSymbolicReferencesSupplier; } private void addInstruction(CfInstruction instruction) { @@ -1004,9 +1025,20 @@ new CfConstMethodHandle( DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder))); } else if (cst instanceof ConstantDynamic) { + // Each symbolic reference to a dynamically-computed constant has a unique ConstantDynamic + // instance from ASM, even when they are equal (i.e. all their components are equal). See + // ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest + // for an example. + Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences = + constantDynamicSymbolicReferencesSupplier.get(); + int symbolicReferenceId = constantDynamicSymbolicReferences.getOrDefault(cst, -1); + if (symbolicReferenceId == -1) { + symbolicReferenceId = constantDynamicSymbolicReferences.size(); + constantDynamicSymbolicReferences.put((ConstantDynamic) cst, symbolicReferenceId); + } addInstruction( CfConstDynamic.fromAsmConstantDynamic( - (ConstantDynamic) cst, application, method.holder)); + symbolicReferenceId, (ConstantDynamic) cst, application, method.holder)); } else { throw new CompilationError("Unsupported constant: " + cst.toString()); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java index 1872754..93572c6 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -59,7 +59,7 @@ public void convertClassInitializers(ExecutorService executorService) throws ExecutionException { if (!classInitializers.isEmpty()) { - IRConverter converter = new IRConverter(createAppViewForConversion(), Timing.empty()); + IRConverter converter = new IRConverter(createAppViewForConversion()); ThreadUtils.processItems( classInitializers, method -> processMethod(method, converter), executorService); } @@ -68,7 +68,7 @@ public void convertInstanceInitializers(ExecutorService executorService) throws ExecutionException { if (!instanceInitializers.isEmpty()) { - IRConverter converter = new IRConverter(createAppViewForConversion(), Timing.empty()); + IRConverter converter = new IRConverter(createAppViewForConversion()); ThreadUtils.processItems( instanceInitializers, clazz -> processInstanceInitializers(clazz, converter),
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java index b5eec8c..2ff901b 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -195,7 +195,7 @@ } timing.begin("Remove dead builder references"); AppInfoWithLiveness appInfo = appView.appInfo(); - IRConverter converter = new IRConverter(appView, Timing.empty()); + IRConverter converter = new IRConverter(appView); ThreadUtils.processMap( builders, (builder, dynamicMethod) -> {
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 c2917b8..4291c1c 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
@@ -96,7 +96,6 @@ public final AppView<?> appView; - protected final Timing timing; public final Outliner outliner; private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization; protected final CfInstructionDesugaringCollection instructionDesugaring; @@ -148,11 +147,9 @@ * The argument `appView` is used to determine if whole program optimizations are allowed or not * (i.e., whether we are running R8). See {@link AppView#enableWholeProgramOptimizations()}. */ - public IRConverter(AppView<?> appView, Timing timing) { + public IRConverter(AppView<?> appView) { assert appView.options() != null; assert appView.options().programConsumer != null; - assert timing != null; - this.timing = timing; this.appView = appView; this.options = appView.options(); this.codeRewriter = new CodeRewriter(appView); @@ -290,8 +287,8 @@ : null; } - public IRConverter(AppInfo appInfo, Timing timing) { - this(AppView.createForD8(appInfo), timing); + public IRConverter(AppInfo appInfo) { + this(AppView.createForD8(appInfo)); } public Inliner getInliner() { @@ -428,10 +425,6 @@ } } - String logCode(InternalOptions options, DexEncodedMethod method) { - return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString(); - } - // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct. public Timing processDesugaredMethod( ProgramMethod method,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java index c243d46..86a4feb 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.ThreadUtils.WorkLoad; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -79,11 +80,11 @@ } @FunctionalInterface - public interface MethodAction<E extends Exception> { - void accept(ProgramMethod method, MethodProcessingContext methodProcessingContext) throws E; + public interface MethodAction { + void accept(ProgramMethod method, MethodProcessingContext methodProcessingContext); } - public <E extends Exception> void forEachWaveWithExtension(MethodAction<E> consumer) throws E { + public void forEachWaveWithExtension(MethodAction consumer) { while (!wave.isEmpty()) { for (ProgramMethod method : wave) { consumer.accept(method, processorContext.createMethodProcessingContext(method)); @@ -92,13 +93,15 @@ } } - public <E extends Exception> void forEachWaveWithExtension( - MethodAction<E> consumer, ExecutorService executorService) throws ExecutionException { + public void forEachWaveWithExtension(MethodAction consumer, ExecutorService executorService) + throws ExecutionException { while (!wave.isEmpty()) { ThreadUtils.processItems( wave, - method -> consumer.accept(method, processorContext.createMethodProcessingContext(method)), - executorService); + (method, ignored) -> + consumer.accept(method, processorContext.createMethodProcessingContext(method)), + executorService, + WorkLoad.HEAVY); prepareForWaveExtensionProcessing(); } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java index b2959f2..cf8e52d 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -45,8 +45,11 @@ public class PrimaryD8L8IRConverter extends IRConverter { + private final Timing timing; + public PrimaryD8L8IRConverter(AppView<AppInfo> appView, Timing timing) { - super(appView, timing); + super(appView); + this.timing = timing; } public void convert(AppView<AppInfo> appView, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index 1ad9a82..0ad28d4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -26,8 +26,11 @@ public class PrimaryR8IRConverter extends IRConverter { + private final Timing timing; + public PrimaryR8IRConverter(AppView<? extends AppInfoWithClassHierarchy> appView, Timing timing) { - super(appView, timing); + super(appView); + this.timing = timing; } public void optimize(AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java index ed08c4d..478e4cf 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -15,24 +15,24 @@ import java.util.Objects; public class ConstantDynamicReference implements StructuralItem<ConstantDynamicReference> { + private final int symbolicReferenceId; private final DexString name; private final DexType type; private final DexMethodHandle bootstrapMethod; private final List<DexValue> bootstrapMethodArguments; private static void specify(StructuralSpecification<ConstantDynamicReference, ?> spec) { - spec.withItem(ConstantDynamicReference::getName) - .withItem(ConstantDynamicReference::getType) - .withItem(ConstantDynamicReference::getBootstrapMethod) - .withItemCollection(ConstantDynamicReference::getBootstrapMethodArguments); + spec.withInt(c -> c.symbolicReferenceId); } public ConstantDynamicReference( + int symbolicReferenceId, DexString name, DexType type, DexMethodHandle bootstrapMethod, List<DexValue> bootstrapMethodArguments) { assert bootstrapMethodArguments.isEmpty(); + this.symbolicReferenceId = symbolicReferenceId; this.name = name; this.type = type; this.bootstrapMethod = bootstrapMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java index c92c9ce..02f581d 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -11,8 +11,8 @@ import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode; import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor; +import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ThreadUtils; -import com.google.common.collect.Iterables; import java.util.Collection; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -51,7 +51,7 @@ ExecutorService executorService) throws ExecutionException { ThreadUtils.processItems( - Iterables.filter(programClasses, (DexProgramClass clazz) -> shouldProcess(clazz, flavour)), + ListUtils.filter(programClasses, clazz -> shouldProcess(clazz, flavour)), clazz -> classProcessor.process(clazz, eventConsumer), executorService); classProcessor.finalizeProcessing(eventConsumer, executorService);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java index d8a62a0..0c1c56f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -45,7 +45,7 @@ private RecordFieldValuesRewriter(AppView<AppInfoWithLiveness> appView) { this.appView = appView; - irConverter = new IRConverter(appView, Timing.empty()); + irConverter = new IRConverter(appView); } // Called after final tree shaking, prune and minify field names and field values.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java index 6e7bf18..d033ca6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.ir.conversion.MethodOptimizationFeedback; import com.android.tools.r8.shaking.AppInfoWithLivenessModifier; import com.android.tools.r8.utils.ThreadUtils; +import java.util.Collection; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; @@ -55,7 +56,7 @@ } public void fixupOptimizationInfos( - Iterable<DexProgramClass> classes, + Collection<DexProgramClass> classes, ExecutorService executorService, OptimizationInfoFixer fixer) throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java index 7bae38e..a9d515a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.kotlin; +import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo; import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo; import static com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.getFlavour; @@ -18,9 +19,11 @@ import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.DexValue.DexValueArray; import com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.Flavour; +import com.android.tools.r8.utils.StringDiagnostic; import java.util.IdentityHashMap; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Supplier; import kotlin.Metadata; import kotlinx.metadata.InconsistentKotlinMetadataException; import kotlinx.metadata.jvm.KotlinClassMetadata; @@ -34,16 +37,58 @@ private static final int SYNTHETIC_CLASS_KIND = 3; public static KotlinClassLevelInfo getKotlinInfo( - DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode) - throws KotlinMetadataException { + AppView<?> appView, + DexClass clazz, + Consumer<DexEncodedMethod> keepByteCode, + Supplier<Boolean> reportUnknownMetadata) { DexAnnotation meta = clazz.annotations().getFirstMatching(appView.dexItemFactory().kotlinMetadataType); - return meta != null ? getKotlinInfo(clazz, appView, keepByteCode, meta) : getNoKotlinInfo(); + if (meta == null) { + return getNoKotlinInfo(); + } + return getKotlinInfoFromAnnotation(appView, clazz, meta, keepByteCode, reportUnknownMetadata); } - public static KotlinClassLevelInfo getKotlinInfo( - DexClass clazz, + public static KotlinClassLevelInfo getKotlinInfoFromAnnotation( AppView<?> appView, + DexClass clazz, + DexAnnotation meta, + Consumer<DexEncodedMethod> keepByteCode, + Supplier<Boolean> reportUnknownMetadata) { + try { + return getKotlinInfo(appView, clazz, keepByteCode, meta); + } catch (KotlinMetadataException e) { + if (reportUnknownMetadata.get()) { + appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion()); + } + appView + .reporter() + .info( + new StringDiagnostic( + "Class " + + clazz.type.toSourceString() + + " has malformed kotlin.Metadata: " + + e.getMessage())); + return getInvalidKotlinInfo(); + } catch (Throwable e) { + if (reportUnknownMetadata.get()) { + appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion()); + } + appView + .reporter() + .info( + new StringDiagnostic( + "Unexpected error while reading " + + clazz.type.toSourceString() + + "'s kotlin.Metadata: " + + e.getMessage())); + return getNoKotlinInfo(); + } + } + + private static KotlinClassLevelInfo getKotlinInfo( + AppView<?> appView, + DexClass clazz, Consumer<DexEncodedMethod> keepByteCode, DexAnnotation annotation) throws KotlinMetadataException { @@ -55,8 +100,8 @@ return createKotlinInfo(kotlin, clazz, kMetadata, appView, keepByteCode); } - public static boolean isLambda(AppView<?> appView, DexClass clazz) - throws KotlinMetadataException { + public static boolean isLambda( + AppView<?> appView, DexClass clazz, Supplier<Boolean> reportUnknownMetadata) { DexItemFactory dexItemFactory = appView.dexItemFactory(); Kotlin kotlin = dexItemFactory.kotlin; Flavour flavour = getFlavour(clazz, kotlin); @@ -69,16 +114,31 @@ return false; } Map<DexString, DexAnnotationElement> elementMap = toElementMap(metadataAnnotation.annotation); - if (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND) { - KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, elementMap); - if (kMetadata instanceof SyntheticClass) { - return ((SyntheticClass) kMetadata).isLambda(); + try { + if (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND) { + KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, elementMap); + if (kMetadata instanceof SyntheticClass) { + return ((SyntheticClass) kMetadata).isLambda(); + } } + assert toKotlinClassMetadata(kotlin, elementMap) instanceof SyntheticClass + == (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND) + : "Synthetic class kinds should agree"; + return false; + } catch (KotlinMetadataException exception) { + if (reportUnknownMetadata.get()) { + appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion()); + } + appView + .reporter() + .info( + new StringDiagnostic( + "Class " + + clazz.type.toSourceString() + + " has malformed kotlin.Metadata: " + + exception.getMessage())); + return false; } - assert toKotlinClassMetadata(kotlin, elementMap) instanceof SyntheticClass - == (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND) - : "Synthetic class kinds should agree"; - return false; } public static boolean hasKotlinClassMetadataAnnotation(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java index 6b5d589..dd7f94a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.kotlin; import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo; import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo; import com.android.tools.r8.errors.Unreachable; @@ -27,9 +26,9 @@ import com.android.tools.r8.shaking.Enqueuer; import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier; import com.android.tools.r8.shaking.KeepClassInfo; -import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.Sets; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis { @@ -38,7 +37,7 @@ private final AppView<?> appView; private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier; private final Set<DexType> prunedTypes; - private boolean reportedUnknownMetadataVersion; + private final AtomicBoolean reportedUnknownMetadataVersion = new AtomicBoolean(false); public KotlinMetadataEnqueuerExtension( AppView<?> appView, @@ -69,52 +68,30 @@ enqueuer.forAllLiveClasses( clazz -> { assert clazz.getKotlinInfo().isNoKotlinInformation(); - try { - if (enqueuer - .getKeepInfo(clazz) - .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) { - if (KotlinClassMetadataReader.isLambda(appView, clazz) - && clazz.hasClassInitializer()) { - feedback.classInitializerMayBePostponed(clazz.getClassInitializer()); - } - clazz.clearKotlinInfo(); - clazz.removeAnnotations( - annotation -> - annotation.getAnnotationType() - == appView.dexItemFactory().kotlinMetadataType); - } else { - clazz.setKotlinInfo( - KotlinClassMetadataReader.getKotlinInfo( - clazz, - appView, - method -> keepByteCodeFunctions.add(method.getReference()))); - if (clazz.getEnclosingMethodAttribute() != null - && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) { - localOrAnonymousClasses.add(clazz); - } + if (enqueuer + .getKeepInfo(clazz) + .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) { + if (KotlinClassMetadataReader.isLambda( + appView, clazz, () -> reportedUnknownMetadataVersion.getAndSet(true)) + && clazz.hasClassInitializer()) { + feedback.classInitializerMayBePostponed(clazz.getClassInitializer()); } - } catch (KotlinMetadataException e) { - appView - .reporter() - .info( - new StringDiagnostic( - "Class " - + clazz.type.toSourceString() - + " has malformed kotlin.Metadata: " - + e.getMessage())); - clazz.setKotlinInfo(getInvalidKotlinInfo()); - reportUnknownMetadataVersion(); - } catch (Throwable e) { - appView - .reporter() - .info( - new StringDiagnostic( - "Unexpected error while reading " - + clazz.type.toSourceString() - + "'s kotlin.Metadata: " - + e.getMessage())); - clazz.setKotlinInfo(getNoKotlinInfo()); - reportUnknownMetadataVersion(); + clazz.clearKotlinInfo(); + clazz.removeAnnotations( + annotation -> + annotation.getAnnotationType() + == appView.dexItemFactory().kotlinMetadataType); + } else { + clazz.setKotlinInfo( + KotlinClassMetadataReader.getKotlinInfo( + appView, + clazz, + method -> keepByteCodeFunctions.add(method.getReference()), + () -> reportedUnknownMetadataVersion.getAndSet(true))); + if (clazz.getEnclosingMethodAttribute() != null + && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) { + localOrAnonymousClasses.add(clazz); + } } }); for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) { @@ -167,13 +144,6 @@ }); } - private void reportUnknownMetadataVersion() { - if (!reportedUnknownMetadataVersion) { - reportedUnknownMetadataVersion = true; - appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion()); - } - } - public class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier { private final ProgramDefinition context;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java index e57fc63..858a57c 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.graph.DexValue.DexValueArray; import com.android.tools.r8.graph.DexValue.DexValueInt; import com.android.tools.r8.graph.DexValue.DexValueString; +import com.android.tools.r8.utils.BooleanBox; import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.ThreadUtils; @@ -130,6 +131,7 @@ return; } final WriteMetadataFieldInfo writeMetadataFieldInfo = WriteMetadataFieldInfo.rewriteAll(); + BooleanBox reportedUnknownMetadataVersion = new BooleanBox(); ThreadUtils.processItems( appView.appInfo().classes(), clazz -> { @@ -138,8 +140,12 @@ return; } KotlinClassLevelInfo kotlinInfo = - KotlinClassMetadataReader.getKotlinInfo( - clazz, appView, ConsumerUtils.emptyConsumer(), metadata); + KotlinClassMetadataReader.getKotlinInfoFromAnnotation( + appView, + clazz, + metadata, + ConsumerUtils.emptyConsumer(), + reportedUnknownMetadataVersion::getAndSet); if (kotlinInfo == getNoKotlinInfo()) { return; }
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java index e4b384f..7b5a3ef 100644 --- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java +++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -12,8 +12,9 @@ import com.android.tools.r8.graph.GenericSignatureContextBuilder; import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier; import com.android.tools.r8.graph.GenericSignatureTypeRewriter; -import com.android.tools.r8.utils.IterableUtils; +import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ThreadUtils; +import java.util.Collection; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.function.BiPredicate; @@ -35,7 +36,8 @@ this.contextBuilder = contextBuilder; } - public void runForD8(Iterable<? extends DexProgramClass> classes, ExecutorService executorService) + public void runForD8( + Collection<? extends DexProgramClass> classes, ExecutorService executorService) throws ExecutionException { if (appView.getNamingLens().isIdentityLens()) { return; @@ -43,7 +45,7 @@ run(classes, executorService); } - public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService) + public void run(Collection<? extends DexProgramClass> classes, ExecutorService executorService) throws ExecutionException { // Rewrite signature annotations for applications that are minified or if we have liveness // information, since we could have pruned types. @@ -66,7 +68,7 @@ ThreadUtils.processItems( // Final merging of classes can introduce pruned types that still exists in classes, we // therefore prune them from work here. - IterableUtils.filter(classes, clazz -> !wasPruned.test(clazz.getType())), + ListUtils.filter(classes, clazz -> !wasPruned.test(clazz.getType())), clazz -> { GenericSignaturePartialTypeArgumentApplier classArgumentApplier = contextBuilder != null
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java index 815a807..763c40f 100644 --- a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java +++ b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
@@ -3,8 +3,6 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.optimize; -import static com.android.tools.r8.utils.ThreadUtils.processItems; - import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; @@ -21,6 +19,7 @@ import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.KeepMethodInfo; +import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -134,7 +133,7 @@ RedundantBridgeRemovalLens.Builder lensBuilder, ExecutorService executorService) throws ExecutionException { Map<DexProgramClass, ProgramMethodSet> bridgesToRemove = new ConcurrentHashMap<>(); - processItems( + ThreadUtils.processItems( appView.appInfo().classes(), clazz -> { ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java index dfc170e..cbc4940 100644 --- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java +++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ThreadUtils; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -49,7 +50,7 @@ } public List<ProgramDefinition> run( - Iterable<DexProgramClass> classes, ExecutorService executorService) + Collection<DexProgramClass> classes, ExecutorService executorService) throws ExecutionException { assert failed.isEmpty();
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index 41c48cb..650aa8b 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -32,6 +32,7 @@ import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.ThreadUtils.WorkLoad; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramFieldMap; import com.android.tools.r8.utils.collections.ProgramFieldSet; @@ -237,8 +238,9 @@ new ConcurrentHashMap<>(); ThreadUtils.processItems( methodsToProcess, - method -> rewriteMethod(method, initializedClassesWithContexts, prunedFields), - executorService); + (method, ignored) -> rewriteMethod(method, initializedClassesWithContexts, prunedFields), + executorService, + WorkLoad.HEAVY); // Register new InitClass instructions. initializedClassesWithContexts.forEach(
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java index 95105f8..301d7b4 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -65,7 +65,7 @@ // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors. public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT = generator.forNonSharableInstanceClass("$IA"); - public final SyntheticKind CONST_DYNAMIC = generator.forInstanceClass("$Condy"); + public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy"); // Method synthetics. public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java index 5ad9494..554176e 100644 --- a/src/main/java/com/android/tools/r8/utils/BooleanBox.java +++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -59,4 +59,10 @@ public boolean isAssigned() { return assigned; } + + public Boolean getAndSet() { + boolean current = get(); + set(); + return current; + } }
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java index b0a6710..45c36b6 100644 --- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.utils.ListUtils.ReferenceAndIntConsumer; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -16,9 +17,37 @@ import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; +import java.util.function.Consumer; public class ThreadUtils { + public enum WorkLoad { + // The threshold for HEAVY is basically just a fan-out when we have two items to process. + HEAVY(2), + // The threshold for LIGHT has been found by running TiviIncremental benchmark in different + // configurations. For partitioning inputs in buckets of 3 and use threading on 4 or more was + // slightly better than threading on 3: + // Buckets of 3 with threshold 4: + // TiviIncrementalLibrary(RunTimeRaw): 28076 ms + // TiviIncrementalMerge(RunTimeRaw): 1429 ms + // TiviIncrementalProgram(RunTimeRaw): 26374 ms + // Buckets of 3 with threshold 3: + // TiviIncrementalLibrary(RunTimeRaw): 30347 ms + // TiviIncrementalMerge(RunTimeRaw): 1558 ms + // TiviIncrementalProgram(RunTimeRaw): 26638 ms + LIGHT(4); + + private final int threshold; + + WorkLoad(int threshold) { + this.threshold = threshold; + } + + public int getThreshold() { + return threshold; + } + } + public static final int NOT_SPECIFIED = -1; public static <T> Future<T> processAsynchronously( @@ -71,18 +100,26 @@ return awaitFuturesWithResults(futures); } - public static <T, E extends Exception> void processItems( - Iterable<T> items, ThrowingConsumer<T, E> consumer, ExecutorService executorService) + public static <T> void processItems( + Collection<T> items, Consumer<T> consumer, ExecutorService executorService) throws ExecutionException { - processItems(items, (item, i) -> consumer.accept(item), executorService); + processItems(items, (item, i) -> consumer.accept(item), executorService, WorkLoad.LIGHT); } - public static <T, E extends Exception> void processItems( - Iterable<T> items, - ThrowingReferenceIntConsumer<T, E> consumer, - ExecutorService executorService) + public static <T> void processItems( + Collection<T> items, + ReferenceAndIntConsumer<T> consumer, + ExecutorService executorService, + WorkLoad workLoad) throws ExecutionException { - processItems(items::forEach, consumer, executorService); + if (items.size() >= workLoad.getThreshold()) { + processItems(items::forEach, consumer::accept, executorService); + } else { + int counter = 0; + for (T item : items) { + consumer.accept(item, counter++); + } + } } public static <T, E extends Exception> void processItems(
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest.java new file mode 100644 index 0000000..5ba0396 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest.java
@@ -0,0 +1,141 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.DesugarTestConfiguration; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +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 ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + private static final Class<?> MAIN_CLASS = Main.class; + private static final String EXPECTED_OUTPUT = StringUtils.lines("true"); + + @Test + public void testReference() throws Exception { + parameters.assumeJvmTestParameters(); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + testForJvm(parameters) + .addProgramClassFileData(getTransformedClasses()) + .addProgramClasses(Main.class) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testDesugaring() throws Exception { + testForDesugaring(parameters) + .addProgramClassFileData(getTransformedClasses()) + .addProgramClasses(Main.class) + .run(parameters.getRuntime(), MAIN_CLASS) + .applyIf( + // When not desugaring the CF code requires JDK 11. + DesugarTestConfiguration::isNotDesugared, + r -> { + if (parameters.isCfRuntime() + && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) { + r.assertSuccessWithOutput(EXPECTED_OUTPUT); + } else { + r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class); + } + }) + .applyIf( + DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + @Test + public void testR8() throws Exception { + parameters.assumeR8TestParameters(); + testForR8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .addProgramClasses(Main.class) + .setMinApi(parameters) + .addKeepMainRule(MAIN_CLASS) + // TODO(b/198142613): There should not be a warnings on class references which are + // desugared away. + .applyIf( + parameters.getApiLevel().isLessThan(AndroidApiLevel.O), + b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup")) + // TODO(b/198142625): Support CONSTANT_Dynamic output for class files. + .applyIf( + parameters.isCfRuntime(), + r -> { + assertThrows( + CompilationFailedException.class, + () -> + r.compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + diagnosticMessage( + containsString( + "Unsupported dynamic constant (not desugaring)"))); + })); + }, + r -> + r.run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + // When ASM writes two dynamic constants with the same BSM and arguments they are canonicialized + // into the same symbolic reference. + private byte[] getTransformedClasses() throws IOException { + return transformer(A.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", A.class, "myConstant", false, "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy2", A.class, "myConstant", false, "constantName", Object.class) + .transform(); + } + + public static class Main { + + public static void main(String[] args) { + // This prints "true" due to the ASM canonicalization of the ConstantDynamic when writing. + System.out.println(A.getConstant1() == A.getConstant2()); + } + } + + public static class A { + + public static Object getConstant1() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object getConstant2() { + return "condy2"; // Will be transformed to Constant_DYNAMIC. + } + + private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) { + return new Object(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest.java new file mode 100644 index 0000000..3bf967d --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest.java
@@ -0,0 +1,592 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.DesugarTestConfiguration; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +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 ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest + extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + private static final String MAIN_CLASS = "A"; + private static final String EXPECTED_OUTPUT = StringUtils.lines("false"); + + @Test + public void testReference() throws Exception { + parameters.assumeJvmTestParameters(); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + testForJvm(parameters) + .addProgramClassFileData(classFileData) + .disassemble() + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testDesugaring() throws Exception { + testForDesugaring(parameters) + .addProgramClassFileData(classFileData) + .run(parameters.getRuntime(), MAIN_CLASS) + .applyIf( + // When not desugaring the CF code requires JDK 11. + DesugarTestConfiguration::isNotDesugared, + r -> { + if (parameters.isCfRuntime() + && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) { + r.assertSuccessWithOutput(EXPECTED_OUTPUT); + } else { + r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class); + } + }) + .applyIf( + DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + @Test + public void testR8() throws Exception { + parameters.assumeR8TestParameters(); + testForR8(parameters.getBackend()) + .addProgramClassFileData(classFileData) + .setMinApi(parameters) + .addKeepMainRule(MAIN_CLASS) + // TODO(b/198142613): There should not be a warnings on class references which are + // desugared away. + .applyIf( + parameters.getApiLevel().isLessThan(AndroidApiLevel.O), + b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup")) + // TODO(b/198142625): Support CONSTANT_Dynamic output for class files. + .applyIf( + parameters.isCfRuntime(), + r -> { + assertThrows( + CompilationFailedException.class, + () -> + r.compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + diagnosticMessage( + containsString( + "Unsupported dynamic constant (not desugaring)"))); + })); + }, + r -> + r.run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + /* + This test was supposed to use the following test classes to have two dynamic constants + using exactly the same bootstrap method and arguments as set up in getTransformedClasses + below. However, ASM will canonicalize both dynamic constants and bootstrap methods, so instead + a class file directly from bytes is used. + */ + + private byte[] getTransformedClasses() throws IOException { + return transformer(A.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", A.class, "myConstant", false, "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy2", A.class, "myConstant", false, "constantName", Object.class) + .transform(); + } + + public static class Main { + + public static void main(String[] args) { + System.out.println(A.getConstant1() == A.getConstant2()); + } + } + + public static class A { + + public static Object getConstant1() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object getConstant2() { + return "condy2"; // Will be transformed to Constant_DYNAMIC. + } + + private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) { + return new Object(); + } + } + +/* + +Class file bytes for the following class file: + +Classfile A.class + Last modified Mar 17, 2023; size 1364 bytes + SHA-256 checksum 4379e359d727521479cee1aa5b6d711090b2777553454d9b667fe502a1b0daa9 + Compiled from "A.java" +public class A + minor version: 0 + major version: 55 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #2 // A + super_class: #4 // java/lang/Object + interfaces: 0, fields: 0, methods: 5, attributes: 3 +Constant pool: + #1 = Utf8 A + #2 = Class #1 // A + #3 = Utf8 java/lang/Object + #4 = Class #3 // java/lang/Object + #5 = Utf8 A.java + #6 = Utf8 java/lang/invoke/MethodHandles$Lookup + #7 = Class #6 // java/lang/invoke/MethodHandles$Lookup + #8 = Utf8 java/lang/invoke/MethodHandles + #9 = Class #8 // java/lang/invoke/MethodHandles + #10 = Utf8 Lookup + #11 = Utf8 <init> + #12 = Utf8 ()V + #13 = NameAndType #11:#12 // "<init>":()V + #14 = Methodref #4.#13 // java/lang/Object."<init>":()V + #15 = Utf8 this + #16 = Utf8 LA; + #17 = Utf8 getConstant1 + #18 = Utf8 ()Ljava/lang/Object; + #19 = Utf8 myConstant + #20 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; + #21 = NameAndType #19:#20 // myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; + #22 = Methodref #2.#21 // A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; + #23 = MethodHandle 6:#22 // REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; + #24 = Utf8 constantName + #25 = Utf8 Ljava/lang/Object; + #26 = NameAndType #24:#25 // constantName:Ljava/lang/Object; + #27 = Dynamic #0:#26 // #0:constantName:Ljava/lang/Object; + #28 = Utf8 getConstant2 + #29 = Dynamic #1:#26 // #1:constantName:Ljava/lang/Object; + #30 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object; + #31 = Utf8 lookup + #32 = Utf8 Ljava/lang/invoke/MethodHandles$Lookup; + #33 = Utf8 name + #34 = Utf8 Ljava/lang/String; + #35 = Utf8 type + #36 = Utf8 Ljava/lang/Class<*>; + #37 = Utf8 Ljava/lang/Class; + #38 = Utf8 main + #39 = Utf8 ([Ljava/lang/String;)V + #40 = Utf8 java/lang/System + #41 = Class #40 // java/lang/System + #42 = Utf8 out + #43 = Utf8 Ljava/io/PrintStream; + #44 = NameAndType #42:#43 // out:Ljava/io/PrintStream; + #45 = Fieldref #41.#44 // java/lang/System.out:Ljava/io/PrintStream; + #46 = NameAndType #17:#18 // getConstant1:()Ljava/lang/Object; + #47 = Methodref #2.#46 // A.getConstant1:()Ljava/lang/Object; + #48 = NameAndType #28:#18 // getConstant2:()Ljava/lang/Object; + #49 = Methodref #2.#48 // A.getConstant2:()Ljava/lang/Object; + #50 = Utf8 java/io/PrintStream + #51 = Class #50 // java/io/PrintStream + #52 = Utf8 [Ljava/lang/String; + #53 = Class #52 // "[Ljava/lang/String;" + #54 = Utf8 println + #55 = Utf8 (Z)V + #56 = NameAndType #54:#55 // println:(Z)V + #57 = Methodref #51.#56 // java/io/PrintStream.println:(Z)V + #58 = Utf8 Code + #59 = Utf8 LineNumberTable + #60 = Utf8 LocalVariableTable + #61 = Utf8 LocalVariableTypeTable + #62 = Utf8 Signature + #63 = Utf8 StackMapTable + #64 = Utf8 InnerClasses + #65 = Utf8 SourceFile + #66 = Utf8 BootstrapMethods +{ + public A(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #14 // Method java/lang/Object."<init>":()V + 4: return + LineNumberTable: + line 132: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this LA; + + public static java.lang.Object getConstant1(); + descriptor: ()Ljava/lang/Object; + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=1, locals=0, args_size=0 + 0: ldc #27 // Dynamic #0:constantName:Ljava/lang/Object; + 2: areturn + LineNumberTable: + line 135: 0 + + public static java.lang.Object getConstant2(); + descriptor: ()Ljava/lang/Object; + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=1, locals=0, args_size=0 + 0: ldc #29 // Dynamic #1:constantName:Ljava/lang/Object; + 2: areturn + LineNumberTable: + line 139: 0 + + private static java.lang.Object myConstant(java.lang.invoke.MethodHandles$Lookup, java.lang.String, java.lang.Class<?>); + descriptor: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=3, args_size=3 + 0: new #4 // class java/lang/Object + 3: dup + 4: invokespecial #14 // Method java/lang/Object."<init>":()V + 7: areturn + LineNumberTable: + line 143: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 8 0 lookup Ljava/lang/invoke/MethodHandles$Lookup; + 0 8 1 name Ljava/lang/String; + 0 8 2 type Ljava/lang/Class; + LocalVariableTypeTable: + Start Length Slot Name Signature + 0 8 2 type Ljava/lang/Class<*>; + Signature: #30 // (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object; + + public static void main(java.lang.String[]); + descriptor: ([Ljava/lang/String;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: getstatic #45 // Field java/lang/System.out:Ljava/io/PrintStream; + 3: invokestatic #47 // Method getConstant1:()Ljava/lang/Object; + 6: invokestatic #49 // Method getConstant2:()Ljava/lang/Object; + 9: if_acmpne 16 + 12: iconst_1 + 13: goto 17 + 16: iconst_0 + 17: invokevirtual #57 // Method java/io/PrintStream.println:(Z)V + 20: return + StackMapTable: number_of_entries = 2 + frame_type = 80 // same_locals_1_stack_item + stack = [ class java/io/PrintStream ] + frame_type = 255 // full_frame + offset_delta = 0 + locals = [ class "[Ljava/lang/String;" ] + stack = [ class java/io/PrintStream, int ] + LineNumberTable: + line 12: 0 + line 13: 20 +} +InnerClasses: +public static final #10= #7 of #9; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles + SourceFile: "A.java" + BootstrapMethods: + 0: #23 REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; + Method arguments: + 1: #23 REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; + Method arguments: +*/ + + private byte[] classFileData = { + -54, -2, -70, -66, 0, 0, 0, 55, 0, 67, 1, 0, 1, 65, 7, 0, 1, + 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, + 101, 99, 116, 7, 0, 3, 1, 0, 6, 65, 46, 106, 97, 118, 97, 1, + 0, 37, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 105, 110, 118, 111, + 107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110, 100, 108, 101, 115, + 36, 76, 111, 111, 107, 117, 112, 7, 0, 6, 1, 0, 30, 106, 97, 118, + 97, 47, 108, 97, 110, 103, 47, 105, 110, 118, 111, 107, 101, 47, 77, 101, + 116, 104, 111, 100, 72, 97, 110, 100, 108, 101, 115, 7, 0, 8, 1, 0, + 6, 76, 111, 111, 107, 117, 112, 1, 0, 6, 60, 105, 110, 105, 116, 62, + 1, 0, 3, 40, 41, 86, 12, 0, 11, 0, 12, 10, 0, 4, 0, 13, + 1, 0, 4, 116, 104, 105, 115, 1, 0, 3, 76, 65, 59, 1, 0, 12, + 103, 101, 116, 67, 111, 110, 115, 116, 97, 110, 116, 49, 1, 0, 20, 40, + 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, + 99, 116, 59, 1, 0, 10, 109, 121, 67, 111, 110, 115, 116, 97, 110, 116, + 1, 0, 94, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 105, + 110, 118, 111, 107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110, 100, + 108, 101, 115, 36, 76, 111, 111, 107, 117, 112, 59, 76, 106, 97, 118, 97, + 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 106, 97, + 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 59, 41, 76, + 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, + 59, 12, 0, 19, 0, 20, 10, 0, 2, 0, 21, 15, 6, 0, 22, 1, + 0, 12, 99, 111, 110, 115, 116, 97, 110, 116, 78, 97, 109, 101, 1, 0, + 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, + 99, 116, 59, 12, 0, 24, 0, 25, 17, 0, 0, 0, 26, 1, 0, 12, + 103, 101, 116, 67, 111, 110, 115, 116, 97, 110, 116, 50, 17, 0, 1, 0, + 26, 1, 0, 97, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, + 105, 110, 118, 111, 107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110, + 100, 108, 101, 115, 36, 76, 111, 111, 107, 117, 112, 59, 76, 106, 97, 118, + 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 106, + 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 60, 42, + 62, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, + 106, 101, 99, 116, 59, 1, 0, 6, 108, 111, 111, 107, 117, 112, 1, 0, + 39, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 105, 110, 118, 111, + 107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110, 100, 108, 101, 115, + 36, 76, 111, 111, 107, 117, 112, 59, 1, 0, 4, 110, 97, 109, 101, 1, + 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, + 105, 110, 103, 59, 1, 0, 4, 116, 121, 112, 101, 1, 0, 20, 76, 106, + 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 60, 42, + 62, 59, 1, 0, 17, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, + 67, 108, 97, 115, 115, 59, 1, 0, 4, 109, 97, 105, 110, 1, 0, 22, + 40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, + 105, 110, 103, 59, 41, 86, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, + 110, 103, 47, 83, 121, 115, 116, 101, 109, 7, 0, 40, 1, 0, 3, 111, + 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, + 105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 12, 0, 42, 0, 43, 9, + 0, 41, 0, 44, 12, 0, 17, 0, 18, 10, 0, 2, 0, 46, 12, 0, + 28, 0, 18, 10, 0, 2, 0, 48, 1, 0, 19, 106, 97, 118, 97, 47, + 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 7, 0, + 50, 1, 0, 19, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, + 83, 116, 114, 105, 110, 103, 59, 7, 0, 52, 1, 0, 7, 112, 114, 105, + 110, 116, 108, 110, 1, 0, 4, 40, 90, 41, 86, 12, 0, 54, 0, 55, + 10, 0, 51, 0, 56, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, + 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, + 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, + 98, 108, 101, 1, 0, 22, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, + 98, 108, 101, 84, 121, 112, 101, 84, 97, 98, 108, 101, 1, 0, 9, 83, + 105, 103, 110, 97, 116, 117, 114, 101, 1, 0, 13, 83, 116, 97, 99, 107, + 77, 97, 112, 84, 97, 98, 108, 101, 1, 0, 12, 73, 110, 110, 101, 114, + 67, 108, 97, 115, 115, 101, 115, 1, 0, 10, 83, 111, 117, 114, 99, 101, + 70, 105, 108, 101, 1, 0, 16, 66, 111, 111, 116, 115, 116, 114, 97, 112, + 77, 101, 116, 104, 111, 100, 115, 0, 33, 0, 2, 0, 4, 0, 0, 0, + 0, 0, 5, 0, 1, 0, 11, 0, 12, 0, 1, 0, 58, 0, 0, 0, + 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 14, -79, 0, 0, + 0, 2, 0, 59, 0, 0, 0, 6, 0, 1, 0, 0, 0, -124, 0, 60, + 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 15, 0, 16, 0, 0, + 0, 9, 0, 17, 0, 18, 0, 1, 0, 58, 0, 0, 0, 27, 0, 1, + 0, 0, 0, 0, 0, 3, 18, 27, -80, 0, 0, 0, 1, 0, 59, 0, + 0, 0, 6, 0, 1, 0, 0, 0, -121, 0, 9, 0, 28, 0, 18, 0, + 1, 0, 58, 0, 0, 0, 27, 0, 1, 0, 0, 0, 0, 0, 3, 18, + 29, -80, 0, 0, 0, 1, 0, 59, 0, 0, 0, 6, 0, 1, 0, 0, + 0, -117, 0, 10, 0, 19, 0, 20, 0, 2, 0, 58, 0, 0, 0, 88, + 0, 2, 0, 3, 0, 0, 0, 8, -69, 0, 4, 89, -73, 0, 14, -80, + 0, 0, 0, 3, 0, 59, 0, 0, 0, 6, 0, 1, 0, 0, 0, -113, + 0, 60, 0, 0, 0, 32, 0, 3, 0, 0, 0, 8, 0, 31, 0, 32, + 0, 0, 0, 0, 0, 8, 0, 33, 0, 34, 0, 1, 0, 0, 0, 8, + 0, 35, 0, 37, 0, 2, 0, 61, 0, 0, 0, 12, 0, 1, 0, 0, + 0, 8, 0, 35, 0, 36, 0, 2, 0, 62, 0, 0, 0, 2, 0, 30, + 0, 9, 0, 38, 0, 39, 0, 1, 0, 58, 0, 0, 0, 75, 0, 3, + 0, 1, 0, 0, 0, 21, -78, 0, 45, -72, 0, 47, -72, 0, 49, -90, + 0, 7, 4, -89, 0, 4, 3, -74, 0, 57, -79, 0, 0, 0, 2, 0, + 63, 0, 0, 0, 20, 0, 2, 80, 7, 0, 51, -1, 0, 0, 0, 1, + 7, 0, 53, 0, 2, 7, 0, 51, 1, 0, 59, 0, 0, 0, 10, 0, + 2, 0, 0, 0, 12, 0, 20, 0, 13, 0, 3, 0, 64, 0, 0, 0, + 10, 0, 1, 0, 7, 0, 9, 0, 10, 0, 25, 0, 65, 0, 0, 0, + 2, 0, 5, 0, 66, 0, 0, 0, 10, 0, 2, 0, 23, 0, 0, 0, + 23, 0, 0 + }; + + /* + + The class file bytes above was generated from the following ASM visitor code using ASM with the + patch below applied (was applied on 443339a964352dcec4dd3915de8f13188920d3ac). + + The thing to note is that the two calls + + methodVisitor.visitLdcInsn(new ConstantDynamic(...)); + + have the exact same arguments, which ASM will canonicalize making it impossible to + write the test using standard ASM visitor. + + import java.nio.file.Files; + import java.nio.file.Paths; + + import org.objectweb.asm.AnnotationVisitor; + import org.objectweb.asm.Attribute; + import org.objectweb.asm.ClassReader; + import org.objectweb.asm.ClassWriter; + import org.objectweb.asm.ConstantDynamic; + import org.objectweb.asm.FieldVisitor; + import org.objectweb.asm.Handle; + import org.objectweb.asm.Label; + import org.objectweb.asm.MethodVisitor; + import org.objectweb.asm.Opcodes; + import org.objectweb.asm.RecordComponentVisitor; + import org.objectweb.asm.Type; + import org.objectweb.asm.TypePath; + + public class DC implements Opcodes { + + public static byte[] dump () throws Exception { + + ClassWriter classWriter = new ClassWriter(0); + FieldVisitor fieldVisitor; + RecordComponentVisitor recordComponentVisitor; + MethodVisitor methodVisitor; + AnnotationVisitor annotationVisitor0; + + classWriter.visit(V11, ACC_PUBLIC | ACC_SUPER, "A", null, "java/lang/Object", null); + + classWriter.visitSource("A.java", null); + + classWriter.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC | ACC_FINAL | ACC_STATIC); + + { + methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(132, label0); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitInsn(RETURN); + Label label1 = new Label(); + methodVisitor.visitLabel(label1); + methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0); + methodVisitor.visitMaxs(1, 1); + methodVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "getConstant1", "()Ljava/lang/Object;", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(135, label0); + methodVisitor.visitLdcInsn(new ConstantDynamic("constantName", "Ljava/lang/Object;", new Handle(Opcodes.H_INVOKESTATIC, "A", "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false), new Object[] {})); + methodVisitor.visitInsn(ARETURN); + methodVisitor.visitMaxs(1, 0); + methodVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "getConstant2", "()Ljava/lang/Object;", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(139, label0); + methodVisitor.visitLdcInsn(new ConstantDynamic("constantName", "Ljava/lang/Object;", new Handle(Opcodes.H_INVOKESTATIC, "A", "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false), new Object[] {})); + methodVisitor.visitInsn(ARETURN); + methodVisitor.visitMaxs(1, 0); + methodVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC, "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object;", null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(143, label0); + methodVisitor.visitTypeInsn(NEW, "java/lang/Object"); + methodVisitor.visitInsn(DUP); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitInsn(ARETURN); + Label label1 = new Label(); + methodVisitor.visitLabel(label1); + methodVisitor.visitLocalVariable("lookup", "Ljava/lang/invoke/MethodHandles$Lookup;", null, label0, label1, 0); + methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 1); + methodVisitor.visitLocalVariable("type", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", label0, label1, 2); + methodVisitor.visitMaxs(2, 3); + methodVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(12, label0); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitMethodInsn(INVOKESTATIC, "A", "getConstant1", "()Ljava/lang/Object;", false); + methodVisitor.visitMethodInsn(INVOKESTATIC, "A", "getConstant2", "()Ljava/lang/Object;", false); + Label label1 = new Label(); + methodVisitor.visitJumpInsn(IF_ACMPNE, label1); + methodVisitor.visitInsn(ICONST_1); + Label label2 = new Label(); + methodVisitor.visitJumpInsn(GOTO, label2); + methodVisitor.visitLabel(label1); + methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/io/PrintStream"}); + methodVisitor.visitInsn(ICONST_0); + methodVisitor.visitLabel(label2); + methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 2, new Object[] {"java/io/PrintStream", Opcodes.INTEGER}); + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false); + Label label3 = new Label(); + methodVisitor.visitLabel(label3); + methodVisitor.visitLineNumber(13, label3); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(3, 1); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + + public static void main(String[] args) throws Exception { + Files.write(Paths.get("A.class"), DC.dump()); + } + } + + diff --git a/asm/src/main/java/org/objectweb/asm/SymbolTable.java b/asm/src/main/java/org/objectweb/asm/SymbolTable.java +index a2f26f18..999620c5 100644 +--- a/asm/src/main/java/org/objectweb/asm/SymbolTable.java ++++ b/asm/src/main/java/org/objectweb/asm/SymbolTable.java +@@ -922,17 +922,6 @@ final class SymbolTable { + private Symbol addConstantDynamicOrInvokeDynamicReference( + final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) { + int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex); +- Entry entry = get(hashCode); +- while (entry != null) { +- if (entry.tag == tag +- && entry.hashCode == hashCode +- && entry.data == bootstrapMethodIndex +- && entry.name.equals(name) +- && entry.value.equals(descriptor)) { +- return entry; +- } +- entry = entry.next; +- } + constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor)); + return put( + new Entry( +@@ -1094,24 +1083,6 @@ final class SymbolTable { + private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) { + final byte[] bootstrapMethodsData = bootstrapMethods.data; +- Entry entry = get(hashCode); +- while (entry != null) { +- if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) { +- int otherOffset = (int) entry.data; +- boolean isSameBootstrapMethod = true; +- for (int i = 0; i < length; ++i) { +- if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) { +- isSameBootstrapMethod = false; +- break; +- } +- } +- if (isSameBootstrapMethod) { +- bootstrapMethods.length = offset; // Revert to old position. +- return entry; +- } +- } +- entry = entry.next; +- } + return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode)); + } + +*/ + +}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicRegress272346803Test.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicRegress272346803Test.java new file mode 100644 index 0000000..bec0148 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicRegress272346803Test.java
@@ -0,0 +1,159 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.DesugarTestConfiguration; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ConstantDynamicRegress272346803Test extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + private static final Class<?> MAIN_CLASS = Main.class; + private static final String EXPECTED_OUTPUT = StringUtils.lines("A", "B"); + + @Test + public void testReference() throws Exception { + parameters.assumeJvmTestParameters(); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + testForJvm(parameters) + .addProgramClasses(Main.class) + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testDesugaring() throws Exception { + testForDesugaring(parameters) + .addProgramClasses(Main.class) + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), MAIN_CLASS) + .applyIf( + // When not desugaring the CF code requires JDK 11. + DesugarTestConfiguration::isNotDesugared, + r -> { + if (parameters.isCfRuntime() + && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) { + r.assertSuccessWithOutput(EXPECTED_OUTPUT); + } else { + r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class); + } + }) + .applyIf( + DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + @Test + public void testR8() throws Exception { + parameters.assumeR8TestParameters(); + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters) + .addKeepMainRule(MAIN_CLASS) + // Access modification is required for inlining the get method from the desugared constant + // dynamic class. + .allowAccessModification() + // TODO(b/198142613): There should not be a warnings on class references which are + // desugared away. + .applyIf( + parameters.getApiLevel().isLessThan(AndroidApiLevel.O), + b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup")) + // TODO(b/198142625): Support CONSTANT_Dynamic output for class files. + .applyIf( + parameters.isCfRuntime(), + r -> { + assertThrows( + CompilationFailedException.class, + () -> + r.compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + diagnosticMessage( + containsString( + "Unsupported dynamic constant (not desugaring)"))); + })); + }, + r -> + r.run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + private List<byte[]> getTransformedClasses() throws IOException { + return Arrays.asList( + transformer(A.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", A.class, "myConstant", false, "constantName", String.class) + .transform(), + transformer(B.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", B.class, "myConstant", false, "constantName", String.class) + .transform()); + } + + // When R8 optimize this code the getter for the two constant dynamics will be inlined into + // Main.main. This leaves the synthetic constant dynamic classes with just two static fields. + // The synthetic sharing cannot share these two synthetics as that would leave only one constant. + // See b/272346803 for details. + public static class Main { + + public static void main(String[] args) { + System.out.println(A.getConstant()); + System.out.println(B.getConstant()); + } + } + + public static class A { + + public static String getConstant() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) { + return "A"; + } + } + + public static class B { + + public static String getConstant() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) { + return "B"; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java index d11c68e..0d23c77 100644 --- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java +++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -101,7 +101,7 @@ public String run() throws IOException { Timing timing = Timing.empty(); - IRConverter converter = new IRConverter(appView, timing); + IRConverter converter = new IRConverter(appView); converter.replaceCodeForTesting(code); AndroidApp app = writeDex(); return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
diff --git a/tools/compiledump.py b/tools/compiledump.py index c313cb9..39315c9 100755 --- a/tools/compiledump.py +++ b/tools/compiledump.py
@@ -415,6 +415,8 @@ f.write(line) def clean_config_line(line, minify, optimize, shrink): + if line.lstrip().startswith('#'): + return False if ('-injars' in line or '-libraryjars' in line or '-print' in line or '-applymapping' in line): return True