Merge commit '9593a2af0256a0002b206d229420fe55429569dd' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index ac5cd11..46cc30f 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java
@@ -240,21 +240,9 @@ // Preserve markers from input dex code and add a marker with the current version // if there were class file inputs. - boolean hasClassResources = false; - boolean hasDexResources = false; - for (DexProgramClass dexProgramClass : appView.appInfo().classes()) { - if (dexProgramClass.originatesFromClassResource()) { - hasClassResources = true; - if (hasDexResources) { - break; - } - } else if (dexProgramClass.originatesFromDexResource()) { - hasDexResources = true; - if (hasClassResources) { - break; - } - } - } + boolean hasClassResources = appView.appInfo().app().getFlags().hasReadProgramClassFromCf(); + boolean hasDexResources = appView.appInfo().app().getFlags().hasReadProgramClassFromDex(); + Marker marker = options.getMarker(Tool.D8); Set<Marker> markers = new HashSet<>(appView.dexItemFactory().extractMarkers()); // TODO(b/166617364): Don't add an additional marker when desugaring is turned off.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 22308ef..91f6172 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -46,6 +46,8 @@ import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringCollection; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterLibraryTypeSynthesizor; import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter; import com.android.tools.r8.ir.desugar.records.RecordRewriter; @@ -322,6 +324,19 @@ CfUtilityMethodsForCodeOptimizations.registerSynthesizedCodeReferences( appView.dexItemFactory()); + // Upfront desugaring generation: Generates new program classes to be added in the app. + CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer = + new CfClassSynthesizerDesugaringEventConsumer(); + CfClassSynthesizerDesugaringCollection.create(appView, null) + .synthesizeClasses(executorService, classSynthesizerEventConsumer); + if (appView.getSyntheticItems().hasPendingSyntheticClasses()) { + appView.setAppInfo( + appView + .appInfo() + .rebuildWithClassHierarchy( + appView.getSyntheticItems().commit(appView.appInfo().app()))); + } + List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>(); timing.begin("Strip unused code"); RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java index 02aebd3..fdec52c 100644 --- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java +++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -49,6 +49,7 @@ import com.android.tools.r8.cf.code.CfTryCatch; import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProto; @@ -56,6 +57,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.If.Type; import com.android.tools.r8.ir.code.MemberType; +import com.android.tools.r8.ir.code.Monitor; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.utils.StringUtils; @@ -72,6 +74,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.objectweb.asm.Opcodes; /** Rudimentary printer to print the source representation for creating CfCode object. */ public class CfCodePrinter extends CfPrinter { @@ -198,6 +201,14 @@ return r8Type("FrameType", ImmutableList.of("cf", "code", "CfFrame")); } + private String monitorType() { + return r8Type("Monitor", ImmutableList.of("ir", "code")); + } + + private String asmOpcodesType() { + return type("Opcodes", ImmutableList.of("org", "objectweb", "asm")); + } + private String dexItemFactoryType() { return r8Type("DexItemFactory", "graph"); } @@ -312,6 +323,16 @@ + ")"; } + private String dexField(DexField field) { + return "options.itemFactory.createField(" + + dexType(field.holder) + + ", " + + dexType(field.type) + + ", " + + dexString(field.name) + + ")"; + } + private void ensureComma() { if (pendingComma) { builder.append(","); @@ -363,7 +384,7 @@ @Override public void print(CfConstClass constClass) { - throw new Unimplemented(constClass.getClass().getSimpleName()); + printNewInstruction("CfConstClass", dexType(constClass.getType())); } @Override @@ -378,7 +399,11 @@ @Override public void print(CfMonitor monitor) { - throw new Unimplemented(monitor.getClass().getSimpleName()); + printNewInstruction( + "CfMonitor", + monitor.getType() == Monitor.Type.ENTER + ? monitorType() + ".Type.ENTER" + : monitorType() + ".Type.EXIT"); } @Override @@ -500,7 +525,23 @@ @Override public void print(CfFieldInstruction insn) { - throw new Unimplemented(insn.getClass().getSimpleName()); + printNewInstruction( + "CfFieldInstruction", fieldInstructionOpcode(insn), dexField(insn.getField())); + } + + private String fieldInstructionOpcode(CfFieldInstruction insn) { + switch (insn.getOpcode()) { + case Opcodes.GETSTATIC: + return asmOpcodesType() + ".GETSTATIC"; + case Opcodes.PUTSTATIC: + return asmOpcodesType() + ".PUTSTATIC"; + case Opcodes.GETFIELD: + return asmOpcodesType() + ".GETFIELD"; + case Opcodes.PUTFIELD: + return asmOpcodesType() + ".PUTFIELD"; + default: + throw new Unimplemented(); + } } @Override
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java index b231b6e..4050961 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic; import com.android.tools.r8.graph.ClassKind; import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexApplicationReadFlags; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClasspathClass; import com.android.tools.r8.graph.DexItemFactory; @@ -67,8 +68,7 @@ private final Timing timing; private final AndroidApp inputApp; - private boolean hasReadProgramResourcesFromCf = false; - private boolean hasReadProgramResourcesFromDex = false; + private DexApplicationReadFlags flags; public interface ProgramClassConflictResolver { DexProgramClass resolveClassConflict(DexProgramClass a, DexProgramClass b); @@ -162,9 +162,9 @@ readProguardMap(proguardMap, builder, executorService, futures); ClassReader classReader = new ClassReader(executorService, futures); classReader.readSources(); - hasReadProgramResourcesFromCf = classReader.hasReadProgramResourceFromCf; - hasReadProgramResourcesFromDex = classReader.hasReadProgramResourceFromDex; ThreadUtils.awaitFutures(futures); + flags = classReader.getDexApplicationReadFlags(); + builder.setFlags(flags); classReader.initializeLazyClassCollection(builder); for (ProgramResourceProvider provider : inputApp.getProgramResourceProviders()) { DataResourceProvider dataResourceProvider = provider.getDataResourceProvider(); @@ -211,7 +211,7 @@ } public MainDexInfo readMainDexClasses(DexApplication app) { - return readMainDexClasses(app, hasReadProgramResourcesFromCf); + return readMainDexClasses(app, flags.hasReadProgramClassFromCf()); } public MainDexInfo readMainDexClassesForR8(DexApplication app) { @@ -325,18 +325,24 @@ // Jar application reader to share across all class readers. private final JarApplicationReader application = new JarApplicationReader(options); - // Flag of which input resource types have flowen into the program classes. + // Flag of which input resource types have flown into the program classes. // Note that this is just at the level of the resources having been given. // It is possible to have, e.g., an empty dex file, so no classes, but this will still be true // as there was a dex resource. private boolean hasReadProgramResourceFromCf = false; private boolean hasReadProgramResourceFromDex = false; + private boolean hasReadProgramRecord = false; ClassReader(ExecutorService executorService, List<Future<?>> futures) { this.executorService = executorService; this.futures = futures; } + public DexApplicationReadFlags getDexApplicationReadFlags() { + return new DexApplicationReadFlags( + hasReadProgramResourceFromDex, hasReadProgramResourceFromCf, hasReadProgramRecord); + } + private void readDexSources(List<ProgramResource> dexSources, Queue<DexProgramClass> classes) throws IOException, ResourceException { if (dexSources.isEmpty()) { @@ -376,7 +382,15 @@ } hasReadProgramResourceFromCf = true; JarClassFileReader<DexProgramClass> reader = - new JarClassFileReader<>(application, classes::add, PROGRAM); + new JarClassFileReader<>( + application, + clazz -> { + classes.add(clazz); + if (clazz.isRecord()) { + hasReadProgramRecord = true; + } + }, + PROGRAM); // Read classes in parallel. for (ProgramResource input : classSources) { futures.add(
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java index 30a1c04..c8b99a4 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -599,7 +599,7 @@ assert checkIfObsolete(); DexType holder = method.holder; if (holder.isArrayType()) { - return resolveMethodOnArray(holder, method); + return resolveMethodOnArray(holder, method.getProto(), method.getName()); } DexClass definition = definitionFor(holder); if (definition == null) { @@ -615,9 +615,18 @@ } public MethodResolutionResult resolveMethodOn(DexClass holder, DexMethod method) { + return resolveMethodOn(holder, method.getProto(), method.getName()); + } + + public MethodResolutionResult resolveMethodOn(DexClass holder, DexMethodSignature method) { + return resolveMethodOn(holder, method.getProto(), method.getName()); + } + + public MethodResolutionResult resolveMethodOn( + DexClass holder, DexProto methodProto, DexString methodName) { return holder.isInterface() - ? resolveMethodOnInterface(holder, method) - : resolveMethodOnClass(method, holder); + ? resolveMethodOnInterface(holder, methodProto, methodName) + : resolveMethodOnClass(methodProto, methodName, holder); } /** @@ -646,18 +655,23 @@ * 10.7 of the Java Language Specification</a>. All invokations will have target java.lang.Object * except clone which has no target. */ - private MethodResolutionResult resolveMethodOnArray(DexType holder, DexMethod method) { + private MethodResolutionResult resolveMethodOnArray( + DexType holder, DexProto methodProto, DexString methodName) { assert checkIfObsolete(); assert holder.isArrayType(); - if (method.name == dexItemFactory().cloneMethodName) { + if (methodName == dexItemFactory().cloneMethodName) { return ArrayCloneMethodResult.INSTANCE; } else { - return resolveMethodOnClass(method, dexItemFactory().objectType); + return resolveMethodOnClass(methodProto, methodName, dexItemFactory().objectType); } } public MethodResolutionResult resolveMethodOnClass(DexMethod method) { - return resolveMethodOnClass(method, method.holder); + return resolveMethodOnClass(method.getProto(), method.getName(), method.getHolderType()); + } + + public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexType holder) { + return resolveMethodOnClass(method.getProto(), method.getName(), holder); } /** @@ -671,10 +685,27 @@ * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the * resolved method is used as basis for dispatch. */ - public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexType holder) { + public MethodResolutionResult resolveMethodOnClass( + DexProto methodProto, DexString methodName, DexType holder) { assert checkIfObsolete(); if (holder.isArrayType()) { - return resolveMethodOnArray(holder, method); + return resolveMethodOnArray(holder, methodProto, methodName); + } + DexClass clazz = definitionFor(holder); + if (clazz == null) { + return ClassNotFoundResult.INSTANCE; + } + // Step 1: If holder is an interface, resolution fails with an ICCE. We return null. + if (clazz.isInterface()) { + return IncompatibleClassResult.INSTANCE; + } + return resolveMethodOnClass(methodProto, methodName, clazz); + } + + public MethodResolutionResult resolveMethodOnClass(DexMethodSignature method, DexType holder) { + assert checkIfObsolete(); + if (holder.isArrayType()) { + return resolveMethodOnArray(holder, method.getProto(), method.getName()); } DexClass clazz = definitionFor(holder); if (clazz == null) { @@ -688,11 +719,15 @@ } public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexClass clazz) { - return resolveMethodOnClass(clazz, method.getProto(), method.getName()); + return resolveMethodOnClass(method.getProto(), method.getName(), clazz); + } + + public MethodResolutionResult resolveMethodOnClass(DexMethodSignature method, DexClass clazz) { + return resolveMethodOnClass(method.getProto(), method.getName(), clazz); } public MethodResolutionResult resolveMethodOnClass( - DexClass clazz, DexProto methodProto, DexString methodName) { + DexProto methodProto, DexString methodName, DexClass clazz) { assert checkIfObsolete(); assert !clazz.isInterface(); // Step 2: @@ -862,10 +897,15 @@ } public MethodResolutionResult resolveMethodOnInterface(DexClass definition, DexMethod desc) { + return resolveMethodOnInterface(definition, desc.getProto(), desc.getName()); + } + + public MethodResolutionResult resolveMethodOnInterface( + DexClass definition, DexProto methodProto, DexString methodName) { assert checkIfObsolete(); assert definition.isInterface(); // Step 2: Look for exact method on interface. - DexEncodedMethod result = definition.lookupMethod(desc); + DexEncodedMethod result = definition.lookupMethod(methodProto, methodName); if (result != null) { return new SingleResolutionResult(definition, definition, result); } @@ -874,13 +914,13 @@ if (objectClass == null) { return ClassNotFoundResult.INSTANCE; } - result = objectClass.lookupMethod(desc); + result = objectClass.lookupMethod(methodProto, methodName); if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) { return new SingleResolutionResult(definition, objectClass, result); } // Step 3: Look for maximally-specific superinterface methods or any interface definition. // This is the same for classes and interfaces. - return resolveMethodStep3(definition, desc.getProto(), desc.getName()); + return resolveMethodStep3(definition, methodProto, methodName); } /**
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java index 31b83b2..309a768 100644 --- a/src/main/java/com/android/tools/r8/graph/DexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -32,6 +32,7 @@ public final InternalOptions options; public final DexItemFactory dexItemFactory; + private final DexApplicationReadFlags flags; // Information on the lexicographically largest string referenced from code. public final DexString highestSortingString; @@ -39,11 +40,13 @@ /** Constructor should only be invoked by the DexApplication.Builder. */ DexApplication( ClassNameMapper proguardMap, + DexApplicationReadFlags flags, ImmutableList<DataResourceProvider> dataResourceProviders, InternalOptions options, DexString highestSortingString, Timing timing) { this.proguardMap = proguardMap; + this.flags = flags; this.dataResourceProviders = dataResourceProviders; this.options = options; this.dexItemFactory = options.itemFactory; @@ -119,6 +122,10 @@ return classes; } + public DexApplicationReadFlags getFlags() { + return flags; + } + public abstract DexClass definitionFor(DexType type); public abstract DexProgramClass programDefinitionFor(DexType type); @@ -140,6 +147,7 @@ public final DexItemFactory dexItemFactory; ClassNameMapper proguardMap; final Timing timing; + DexApplicationReadFlags flags; DexString highestSortingString; private final Collection<DexProgramClass> synthesizedClasses; @@ -154,6 +162,7 @@ abstract T self(); public Builder(DexApplication application) { + flags = application.flags; programClasses.addAll(application.programClasses()); dataResourceProviders.addAll(application.dataResourceProviders); proguardMap = application.getProguardMap(); @@ -172,6 +181,10 @@ return null; } + public void setFlags(DexApplicationReadFlags flags) { + this.flags = flags; + } + public synchronized T setProguardMap(ClassNameMapper proguardMap) { assert this.proguardMap == null; this.proguardMap = proguardMap;
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java new file mode 100644 index 0000000..d5089a7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
@@ -0,0 +1,34 @@ +// 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.graph; + +// Flags set based on the application when it was read. +// Note that in r8, once classes are pruned, the flags may not reflect the application anymore. +public class DexApplicationReadFlags { + + private final boolean hasReadProgramClassFromDex; + private final boolean hasReadProgramClassFromCf; + private final boolean hasReadProgramRecord; + + public DexApplicationReadFlags( + boolean hasReadProgramClassFromDex, + boolean hasReadProgramClassFromCf, + boolean hasReadProgramRecord) { + this.hasReadProgramClassFromDex = hasReadProgramClassFromDex; + this.hasReadProgramClassFromCf = hasReadProgramClassFromCf; + this.hasReadProgramRecord = hasReadProgramRecord; + } + + public boolean hasReadProgramClassFromCf() { + return hasReadProgramClassFromCf; + } + + public boolean hasReadProgramClassFromDex() { + return hasReadProgramClassFromDex; + } + + public boolean hasReadProgramRecord() { + return hasReadProgramRecord; + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java index b39a1a7..765fef3 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -59,6 +59,10 @@ return getDefinition().getOptimizationInfo(); } + public DexType getArgumentType(int index) { + return getDefinition().getArgumentType(index); + } + public DexType getParameter(int index) { return getReference().getParameter(index); }
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 c2b204e..0243703 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -403,7 +403,7 @@ } public DexMethodSignature getSignature() { - return new DexMethodSignature(getReference()); + return DexMethodSignature.create(getReference()); } public DexType returnType() { @@ -1469,6 +1469,7 @@ if (from.hasClassFileVersion()) { upgradeClassFileVersion(from.getClassFileVersion()); } + apiLevelForCode = getApiLevelForCode().max(from.getApiLevelForCode()); } public MethodTypeSignature getGenericSignature() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 0100fd5..82fa005 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1122,6 +1122,7 @@ public class JavaUtilArraysMethods { public final DexMethod asList; + public final DexMethod equalsObjectArray; private JavaUtilArraysMethods() { asList = @@ -1130,6 +1131,12 @@ createString("asList"), listDescriptor, new DexString[] {objectArrayDescriptor}); + equalsObjectArray = + createMethod( + arraysDescriptor, + equalsMethodName, + booleanDescriptor, + new DexString[] {objectArrayDescriptor, objectArrayDescriptor}); } } @@ -2254,7 +2261,7 @@ String baseName, DexType holder, DexProto proto, Predicate<DexMethodSignature> isFresh) { return createFreshMember( name -> { - DexMethodSignature trySignature = new DexMethodSignature(proto, name); + DexMethodSignature trySignature = DexMethodSignature.create(name, proto); if (isFresh.test(trySignature)) { return Optional.of(trySignature); } else {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java index c5cb0ff..5eaf805 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -157,7 +157,7 @@ } public DexMethodSignature getSignature() { - return new DexMethodSignature(this); + return DexMethodSignature.create(this); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java b/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java index 74d464f..0ac4558 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java
@@ -9,36 +9,32 @@ import com.android.tools.r8.utils.structural.StructuralSpecification; import java.util.Objects; -public class DexMethodSignature implements StructuralItem<DexMethodSignature> { +public abstract class DexMethodSignature implements StructuralItem<DexMethodSignature> { - private final DexProto proto; - private final DexString name; + DexMethodSignature() {} - public DexMethodSignature(DexMethod method) { - this(method.proto, method.name); + public static DexMethodSignature create(DexMethod method) { + return new MethodBased(method); } - public DexMethodSignature(DexProto proto, DexString name) { - assert proto != null; - assert name != null; - this.proto = proto; - this.name = name; + public static DexMethodSignature create(DexString name, DexProto proto) { + return new NameAndProtoBased(name, proto); } + public abstract DexString getName(); + + public abstract DexProto getProto(); + public int getArity() { - return proto.getArity(); + return getProto().getArity(); } - public DexString getName() { - return name; - } - - public DexProto getProto() { - return proto; + public DexType getParameter(int index) { + return getProto().getParameter(index); } public DexType getReturnType() { - return proto.returnType; + return getProto().getReturnType(); } @Override @@ -51,11 +47,11 @@ } public DexMethodSignature withName(DexString name) { - return new DexMethodSignature(proto, name); + return create(name, getProto()); } public DexMethodSignature withProto(DexProto proto) { - return new DexMethodSignature(proto, name); + return create(getName(), proto); } public DexMethod withHolder(ProgramDefinition definition, DexItemFactory dexItemFactory) { @@ -63,20 +59,20 @@ } public DexMethod withHolder(DexReference reference, DexItemFactory dexItemFactory) { - return dexItemFactory.createMethod(reference.getContextType(), proto, name); + return dexItemFactory.createMethod(reference.getContextType(), getProto(), getName()); } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof DexMethodSignature)) return false; DexMethodSignature that = (DexMethodSignature) o; - return proto == that.proto && name == that.name; + return getProto() == that.getProto() && getName() == that.getName(); } @Override public int hashCode() { - return Objects.hash(proto, name); + return Objects.hash(getProto(), getName()); } @Override @@ -86,7 +82,7 @@ @Override public String toString() { - return "Method Signature " + name + " " + proto.toString(); + return "Method Signature " + getName() + " " + getProto(); } private String toSourceString() { @@ -98,13 +94,53 @@ if (includeReturnType) { builder.append(getReturnType().toSourceString()).append(" "); } - builder.append(name).append("("); + builder.append(getName()).append("("); for (int i = 0; i < getArity(); i++) { if (i != 0) { builder.append(", "); } - builder.append(proto.parameters.values[i].toSourceString()); + builder.append(getProto().parameters.values[i].toSourceString()); } return builder.append(")").toString(); } + + static class MethodBased extends DexMethodSignature { + + private final DexMethod method; + + MethodBased(DexMethod method) { + this.method = method; + } + + @Override + public DexString getName() { + return method.getName(); + } + + @Override + public DexProto getProto() { + return method.getProto(); + } + } + + static class NameAndProtoBased extends DexMethodSignature { + + private final DexString name; + private final DexProto proto; + + NameAndProtoBased(DexString name, DexProto proto) { + this.name = name; + this.proto = proto; + } + + @Override + public DexString getName() { + return name; + } + + @Override + public DexProto getProto() { + return proto; + } + } }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java index ceac26a..f222dbf 100644 --- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -35,6 +35,7 @@ private DirectMappedDexApplication( ClassNameMapper proguardMap, + DexApplicationReadFlags flags, Map<DexType, DexClass> allClasses, ImmutableList<DexProgramClass> programClasses, ImmutableList<DexClasspathClass> classpathClasses, @@ -43,12 +44,7 @@ InternalOptions options, DexString highestSortingString, Timing timing) { - super( - proguardMap, - dataResourceProviders, - options, - highestSortingString, - timing); + super(proguardMap, flags, dataResourceProviders, options, highestSortingString, timing); this.allClasses = Collections.unmodifiableMap(allClasses); this.programClasses = programClasses; this.classpathClasses = classpathClasses; @@ -307,6 +303,7 @@ addAll(allClasses, getProgramClasses()); return new DirectMappedDexApplication( proguardMap, + flags, allClasses, ImmutableList.copyOf(getProgramClasses()), ImmutableList.copyOf(getClasspathClasses()),
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java index 03627d7..702d1fd 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java +++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -66,10 +66,6 @@ return false; } - public boolean isFailedResolution() { - return false; - } - public DexClass getInitialResolutionHolder() { return null; }
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 a520ae3..174dca5 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -62,6 +62,7 @@ import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardKeepAttributes; +import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -125,31 +126,35 @@ @Override public CfCode asCfCode() { if (code == null) { - ReparseContext context = this.context; - JarApplicationReader application = this.application; - assert application != null; - assert context != null; - // The ClassCodeVisitor is in charge of setting this.context to null. - try { - parseCode(context, false); - } catch (JsrEncountered e) { - for (Code code : context.codeList) { - code.asLazyCfCode().code = null; - code.asLazyCfCode().context = context; - code.asLazyCfCode().application = application; - } - try { - parseCode(context, true); - } catch (JsrEncountered e1) { - throw new Unreachable(e1); - } - } - assert verifyNoReparseContext(context.owner); + ExceptionUtils.withOriginAttachmentHandler(origin, this::internalParseCode); } assert code != null; return code; } + private void internalParseCode() { + ReparseContext context = this.context; + JarApplicationReader application = this.application; + assert application != null; + assert context != null; + // The ClassCodeVisitor is in charge of setting this.context to null. + try { + parseCode(context, false); + } catch (JsrEncountered e) { + for (Code code : context.codeList) { + code.asLazyCfCode().code = null; + code.asLazyCfCode().context = context; + code.asLazyCfCode().application = application; + } + try { + parseCode(context, true); + } catch (JsrEncountered e1) { + throw new Unreachable(e1); + } + } + assert verifyNoReparseContext(context.owner); + } + @Override public Code getCodeAsInlining(DexMethod caller, DexMethod callee) { return asCfCode().getCodeAsInlining(caller, callee); @@ -773,6 +778,9 @@ @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { DexMethod method = application.getMethod(owner, name, desc); + if (application.getFactory().isClassConstructor(method)) { + throw new CompilationError("Invalid input code with a call to <clinit>"); + } instructions.add(new CfInvoke(opcode, method, itf)); }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java index 509a10d..cf0d761 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -29,6 +29,7 @@ /** Constructor should only be invoked by the DexApplication.Builder. */ private LazyLoadedDexApplication( ClassNameMapper proguardMap, + DexApplicationReadFlags flags, ProgramClassCollection programClasses, ImmutableList<DataResourceProvider> dataResourceProviders, ClasspathClassCollection classpathClasses, @@ -36,12 +37,7 @@ InternalOptions options, DexString highestSortingString, Timing timing) { - super( - proguardMap, - dataResourceProviders, - options, - highestSortingString, - timing); + super(proguardMap, flags, dataResourceProviders, options, highestSortingString, timing); this.programClasses = programClasses; this.classpathClasses = classpathClasses; this.libraryClasses = libraryClasses; @@ -236,6 +232,7 @@ public LazyLoadedDexApplication build() { return new LazyLoadedDexApplication( proguardMap, + flags, ProgramClassCollection.create(getProgramClasses(), resolver), ImmutableList.copyOf(dataResourceProviders), classpathClasses,
diff --git a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java index c3f3c42..e5467ff 100644 --- a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java +++ b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
@@ -25,6 +25,15 @@ return isAccessibleFrom(context, appView.appInfo()); } + /** + * Returns true if resolution failed. + * + * <p>Note the disclaimer in the doc of {@code MethodResolutionResult.isSingleResolution()}. + */ + public boolean isFailedResolution() { + return false; + } + public boolean isFieldResolutionResult() { return false; }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java index 8f705d4..5c41207 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java +++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -7,41 +7,41 @@ import com.android.tools.r8.utils.IteratorUtils; import com.android.tools.r8.utils.TraversalContinuation; import com.google.common.collect.Lists; +import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceRBTreeMap; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.TreeMap; +import java.util.SortedMap; import java.util.function.Function; import java.util.function.Predicate; public class MethodMapBacking extends MethodCollectionBacking { - private Map<DexMethodSignature, DexEncodedMethod> methodMap; + private SortedMap<DexMethodSignature, DexEncodedMethod> methodMap; public MethodMapBacking() { - this(createMap()); + this(createdLinkedMap()); } - private MethodMapBacking(Map<DexMethodSignature, DexEncodedMethod> methodMap) { + private MethodMapBacking(SortedMap<DexMethodSignature, DexEncodedMethod> methodMap) { this.methodMap = methodMap; } public static MethodMapBacking createSorted() { - return new MethodMapBacking(new TreeMap<>()); + return new MethodMapBacking(new Object2ReferenceRBTreeMap<>()); } - private static Map<DexMethodSignature, DexEncodedMethod> createMap() { + private static SortedMap<DexMethodSignature, DexEncodedMethod> createdLinkedMap() { // Maintain a linked map so the output order remains a deterministic function of the input. - return new HashMap<>(); + return new Object2ReferenceLinkedOpenHashMap<>(); } - private static Map<DexMethodSignature, DexEncodedMethod> createMap(int capacity) { + private static SortedMap<DexMethodSignature, DexEncodedMethod> createdLinkedMap(int capacity) { // Maintain a linked map so the output order remains a deterministic function of the input. - return new HashMap<>(capacity); + return new Object2ReferenceLinkedOpenHashMap<>(capacity); } private void replace(DexMethodSignature existingKey, DexEncodedMethod method) { @@ -115,7 +115,7 @@ @Override DexEncodedMethod getMethod(DexProto methodProto, DexString methodName) { - return methodMap.get(new DexMethodSignature(methodProto, methodName)); + return methodMap.get(DexMethodSignature.create(methodName, methodProto)); } private DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) { @@ -216,7 +216,8 @@ if (methods == null) { methods = DexEncodedMethod.EMPTY_ARRAY; } - Map<DexMethodSignature, DexEncodedMethod> newMap = createMap(size() + methods.length); + SortedMap<DexMethodSignature, DexEncodedMethod> newMap = + createdLinkedMap(size() + methods.length); forEachMethod( method -> { if (belongsToVirtualPool(method)) { @@ -238,7 +239,8 @@ if (methods == null) { methods = DexEncodedMethod.EMPTY_ARRAY; } - Map<DexMethodSignature, DexEncodedMethod> newMap = createMap(size() + methods.length); + SortedMap<DexMethodSignature, DexEncodedMethod> newMap = + createdLinkedMap(size() + methods.length); forEachMethod( method -> { if (belongsToDirectPool(method)) {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java index f1e8ae1..c2cb1d4 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java +++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -59,15 +59,6 @@ return null; } - /** - * Returns true if resolution failed. - * - * <p>Note the disclaimer in the doc of {@code isSingleResolution()}. - */ - public boolean isFailedResolution() { - return false; - } - public boolean isIncompatibleClassChangeErrorResult() { return false; } @@ -84,6 +75,10 @@ return false; } + public boolean isArrayCloneMethodResult() { + return false; + } + /** Returns non-null if isFailedResolution() is true, otherwise null. */ public FailedResolutionResult asFailedResolution() { return null; @@ -808,6 +803,11 @@ public boolean isVirtualTarget() { return true; } + + @Override + public boolean isArrayCloneMethodResult() { + return true; + } } /** Base class for all types of failed resolutions. */
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java index 9e83183..65e9670 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -135,6 +135,11 @@ } @Override + public ClassTypeElement asDefinitelyNotNull() { + return getOrCreateVariant(Nullability.definitelyNotNull()); + } + + @Override public ClassTypeElement asMeetWithNotNull() { return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull())); }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java index 5db6f54..1a32390 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.ir.analysis.type; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.Objects; @@ -19,6 +20,7 @@ public class DynamicType { private static final DynamicType BOTTOM = new DynamicType(TypeElement.getBottom()); + private static final DynamicType NULL_TYPE = new DynamicType(TypeElement.getNull()); private static final DynamicType UNKNOWN = new DynamicType(TypeElement.getTop()); private final TypeElement dynamicUpperBoundType; @@ -29,12 +31,29 @@ } public static DynamicType create( + AppView<AppInfoWithLiveness> appView, TypeElement dynamicUpperBoundType) { + ClassTypeElement dynamicLowerBoundType = null; + if (dynamicUpperBoundType.isClassType()) { + ClassTypeElement dynamicUpperBoundClassType = dynamicUpperBoundType.asClassType(); + DexClass dynamicUpperBoundClass = + appView.definitionFor(dynamicUpperBoundClassType.getClassType()); + if (dynamicUpperBoundClass != null && dynamicUpperBoundClass.isEffectivelyFinal(appView)) { + dynamicLowerBoundType = dynamicUpperBoundClassType; + } + } + return create(appView, dynamicUpperBoundType, dynamicLowerBoundType); + } + + public static DynamicType create( AppView<AppInfoWithLiveness> appView, TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) { if (dynamicUpperBoundType.isBottom()) { return bottom(); } + if (dynamicUpperBoundType.isNullType()) { + return definitelyNull(); + } if (dynamicUpperBoundType.isTop()) { return unknown(); } @@ -47,6 +66,7 @@ return DynamicTypeWithLowerBound.create( appView, dynamicUpperBoundType.asClassType(), dynamicLowerBoundType); } + assert verifyNotEffectivelyFinalClassType(appView, dynamicUpperBoundType); return new DynamicType(dynamicUpperBoundType); } @@ -54,11 +74,12 @@ return new ExactDynamicType(exactDynamicType); } - public static DynamicType create(Value value, AppView<AppInfoWithLiveness> appView) { + public static DynamicType create(AppView<AppInfoWithLiveness> appView, Value value) { assert value.getType().isReferenceType(); TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView); ClassTypeElement dynamicLowerBoundType = - value.getDynamicLowerBoundType(appView, dynamicUpperBoundType.nullability()); + value.getDynamicLowerBoundType( + appView, dynamicUpperBoundType, dynamicUpperBoundType.nullability()); return create(appView, dynamicUpperBoundType, dynamicLowerBoundType); } @@ -66,6 +87,10 @@ return BOTTOM; } + public static DynamicType definitelyNull() { + return NULL_TYPE; + } + public static DynamicType unknown() { return UNKNOWN; } @@ -82,12 +107,16 @@ return null; } + public Nullability getNullability() { + return getDynamicUpperBoundType().nullability(); + } + public boolean isBottom() { return getDynamicUpperBoundType().isBottom(); } - public boolean isTrivial(TypeElement staticType) { - return staticType.equals(getDynamicUpperBoundType()) || isUnknown(); + public boolean isNullType() { + return getDynamicUpperBoundType().isNullType(); } public boolean isUnknown() { @@ -116,6 +145,18 @@ private ClassTypeElement meetDynamicLowerBound( AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) { + if (isNullType()) { + if (dynamicType.hasDynamicLowerBoundType()) { + return dynamicType.getDynamicLowerBoundType().joinNullability(Nullability.definitelyNull()); + } + return null; + } + if (dynamicType.isNullType()) { + if (hasDynamicLowerBoundType()) { + return getDynamicLowerBoundType().joinNullability(Nullability.definitelyNull()); + } + return null; + } if (!hasDynamicLowerBoundType() || !dynamicType.hasDynamicLowerBoundType()) { return null; } @@ -143,4 +184,27 @@ public int hashCode() { return dynamicUpperBoundType.hashCode(); } + + private static boolean verifyNotEffectivelyFinalClassType( + AppView<AppInfoWithLiveness> appView, TypeElement type) { + if (type.isClassType()) { + ClassTypeElement classType = type.asClassType(); + DexClass clazz = appView.definitionFor(classType.getClassType()); + assert clazz == null || !clazz.isEffectivelyFinal(appView); + } + return true; + } + + public DynamicType withNullability(Nullability nullability) { + assert !hasDynamicLowerBoundType(); + if (!getDynamicUpperBoundType().isReferenceType()) { + return this; + } + ReferenceTypeElement dynamicUpperBoundReferenceType = + getDynamicUpperBoundType().asReferenceType(); + if (dynamicUpperBoundReferenceType.nullability() == nullability) { + return this; + } + return new DynamicType(dynamicUpperBoundReferenceType.getOrCreateVariant(nullability)); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java index 400f99e..e4190bc 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
@@ -12,7 +12,7 @@ private final ClassTypeElement dynamicLowerBoundType; - DynamicTypeWithLowerBound( + private DynamicTypeWithLowerBound( ClassTypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) { super(dynamicUpperBoundType); assert !dynamicUpperBoundType.equals(dynamicLowerBoundType); @@ -34,6 +34,11 @@ } @Override + public ClassTypeElement getDynamicUpperBoundType() { + return super.getDynamicUpperBoundType().asClassType(); + } + + @Override public boolean hasDynamicLowerBoundType() { return true; } @@ -44,11 +49,6 @@ } @Override - public boolean isTrivial(TypeElement staticType) { - return false; - } - - @Override public boolean equals(Object other) { if (other == null || getClass() != other.getClass()) { return false; @@ -62,4 +62,14 @@ public int hashCode() { return Objects.hash(getDynamicUpperBoundType(), getDynamicLowerBoundType()); } + + @Override + public DynamicType withNullability(Nullability nullability) { + if (getDynamicUpperBoundType().nullability() == nullability) { + return this; + } + return new DynamicTypeWithLowerBound( + getDynamicUpperBoundType().getOrCreateVariant(nullability), + getDynamicLowerBoundType().getOrCreateVariant(nullability)); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java index a6061d9..d66cfcc 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
@@ -16,13 +16,13 @@ } @Override - public ClassTypeElement getDynamicLowerBoundType() { - return getDynamicUpperBoundType().asClassType(); + public ClassTypeElement getDynamicUpperBoundType() { + return super.getDynamicUpperBoundType().asClassType(); } @Override - public boolean isTrivial(TypeElement staticType) { - return false; + public ClassTypeElement getDynamicLowerBoundType() { + return getDynamicUpperBoundType(); } @Override @@ -38,4 +38,12 @@ public int hashCode() { return getDynamicLowerBoundType().hashCode(); } + + @Override + public DynamicType withNullability(Nullability nullability) { + if (getDynamicUpperBoundType().nullability() == nullability) { + return this; + } + return new ExactDynamicType(getDynamicUpperBoundType().getOrCreateVariant(nullability)); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java index e45ac9a..c3a5298 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
@@ -49,6 +49,10 @@ return isMaybeNull() || isDefinitelyNull(); } + public boolean isUnknown() { + return isMaybeNull(); + } + public Nullability join(Nullability other) { if (this == BOTTOM) { return other; @@ -79,6 +83,10 @@ return join(other) == other; } + public boolean strictlyLessThan(Nullability other) { + return !equals(other) && other == join(other); + } + public static Nullability definitelyNull() { return DEFINITELY_NULL; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index 0447c1a..e5c792f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -27,6 +27,7 @@ import com.android.tools.r8.utils.DequeUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.IteratorUtils; +import com.android.tools.r8.utils.LinkedHashSetUtils; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.StringUtils; @@ -61,7 +62,7 @@ public static class LiveAtEntrySets { // Set of live SSA values (regardless of whether they denote a local variable). - public final Set<Value> liveValues; + public final LinkedHashSet<Value> liveValues; // Subset of live local-variable values. public final Set<Value> liveLocalValues; @@ -69,7 +70,7 @@ public final Deque<Value> liveStackValues; public LiveAtEntrySets( - Set<Value> liveValues, Set<Value> liveLocalValues, Deque<Value> liveStackValues) { + LinkedHashSet<Value> liveValues, Set<Value> liveLocalValues, Deque<Value> liveStackValues) { assert liveValues.containsAll(liveLocalValues); this.liveValues = liveValues; this.liveLocalValues = liveLocalValues; @@ -176,14 +177,14 @@ while (!worklist.isEmpty()) { BasicBlock block = worklist.poll(); // Note that the iteration order of live values matters when inserting spill/restore moves. - Set<Value> live = new LinkedHashSet<>(); + LinkedHashSet<Value> live = new LinkedHashSet<>(); Set<Value> liveLocals = Sets.newIdentityHashSet(); Deque<Value> liveStack = new ArrayDeque<>(); Set<BasicBlock> exceptionalSuccessors = block.getCatchHandlers().getUniqueTargets(); for (BasicBlock succ : block.getSuccessors()) { LiveAtEntrySets liveAtSucc = liveAtEntrySets.get(succ); if (liveAtSucc != null) { - live.addAll(liveAtSucc.liveValues); + LinkedHashSetUtils.addAll(live, liveAtSucc.liveValues); liveLocals.addAll(liveAtSucc.liveLocalValues); // The stack is only allowed to be non-empty in the case of linear-flow (so-far). // If succ is an exceptional successor the successor stack should be empty
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java index 4df7f91..0c58032 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Value.java +++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -1086,7 +1086,7 @@ } public DynamicType getDynamicType(AppView<AppInfoWithLiveness> appView) { - return DynamicType.create(this, appView); + return DynamicType.create(appView, this); } public TypeElement getDynamicUpperBoundType( @@ -1137,18 +1137,23 @@ } public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) { - return getDynamicLowerBoundType(appView, Nullability.maybeNull()); + return getDynamicLowerBoundType(appView, null, Nullability.maybeNull()); } public ClassTypeElement getDynamicLowerBoundType( - AppView<AppInfoWithLiveness> appView, Nullability maxNullability) { - // If it is a final or effectively-final class type, then we know the lower bound. - if (getType().isClassType()) { - ClassTypeElement classType = getType().asClassType(); - DexType type = classType.getClassType(); - DexClass clazz = appView.definitionFor(type); - if (clazz != null && clazz.isEffectivelyFinal(appView)) { - return classType.meetNullability(maxNullability); + AppView<AppInfoWithLiveness> appView, + TypeElement dynamicUpperBoundType, + Nullability maxNullability) { + // If the dynamic upper bound type is a final or effectively-final class type, then we know the + // lower bound. Since the dynamic upper bound type is below the static type in the class + // hierarchy, we only need to check if the dynamic upper bound type is effectively final if it + // is present. + TypeElement upperBoundType = dynamicUpperBoundType != null ? dynamicUpperBoundType : getType(); + if (upperBoundType.isClassType()) { + ClassTypeElement upperBoundClassType = upperBoundType.asClassType(); + DexClass upperBoundClass = appView.definitionFor(upperBoundClassType.getClassType()); + if (upperBoundClass != null && upperBoundClass.isEffectivelyFinal(appView)) { + return upperBoundClassType.meetNullability(maxNullability); } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java index 2a30a23..5d49406 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -9,11 +9,9 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer; -import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer; import com.android.tools.r8.utils.ThreadUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -55,20 +53,17 @@ throws ExecutionException { List<DexProgramClass> classes = appView.appInfo().classes(); - if (appView.options().isDesugaredLibraryCompilation()) { - CfL8ClassSynthesizerEventConsumer l8ClassSynthesizerEventConsumer = - new CfL8ClassSynthesizerEventConsumer(); - converter.l8ClassSynthesis(executorService, l8ClassSynthesizerEventConsumer); + CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer = + new CfClassSynthesizerDesugaringEventConsumer(); + converter.classSynthesisDesugaring(executorService, classSynthesizerEventConsumer); + if (!classSynthesizerEventConsumer.getSynthesizedClasses().isEmpty()) { classes = ImmutableList.<DexProgramClass>builder() .addAll(classes) - .addAll(l8ClassSynthesizerEventConsumer.getSynthesizedClasses()) + .addAll(classSynthesizerEventConsumer.getSynthesizedClasses()) .build(); } - D8CfClassDesugaringEventConsumer classDesugaringEventConsumer = - CfClassDesugaringEventConsumer.createForD8(methodProcessor); - converter.desugarClassesForD8(classes, classDesugaringEventConsumer, executorService); converter.prepareDesugaringForD8(executorService); while (!classes.isEmpty()) {
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 8595ecd..c00f43c 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
@@ -44,13 +44,11 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodConversionOptions.DefaultMethodConversionOptions; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; -import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection; -import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringCollection; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerCollection; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer; import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection; import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer.D8CfPostProcessingDesugaringEventConsumer; @@ -99,7 +97,6 @@ import com.android.tools.r8.ir.regalloc.RegisterAllocator; import com.android.tools.r8.logging.Log; import com.android.tools.r8.naming.IdentifierNameStringMarker; -import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator; import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis; @@ -135,7 +132,6 @@ private final Timing timing; private final Outliner outliner; private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization; - private final CfClassDesugaringCollection classDesugaring; private final CfInstructionDesugaringCollection instructionDesugaring; private final FieldAccessAnalysis fieldAccessAnalysis; private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis; @@ -226,7 +222,6 @@ // - invoke-special desugaring. assert options.desugarState.isOn(); this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView); - this.classDesugaring = CfClassDesugaringCollection.create(appView); this.interfaceMethodRewriter = null; this.covariantReturnTypeAnnotationTransformer = null; this.dynamicTypeOptimization = null; @@ -253,10 +248,6 @@ appView.enableWholeProgramOptimizations() ? CfInstructionDesugaringCollection.empty() : CfInstructionDesugaringCollection.create(appView); - this.classDesugaring = - appView.enableWholeProgramOptimizations() - ? CfClassDesugaringCollection.empty() - : CfClassDesugaringCollection.create(appView); this.interfaceMethodRewriter = options.isInterfaceMethodDesugaringEnabled() && appView.enableWholeProgramOptimizations() ? new InterfaceMethodRewriter(appView, this) @@ -453,19 +444,20 @@ } } - public void l8ClassSynthesis( + public void classSynthesisDesugaring( ExecutorService executorService, - CfL8ClassSynthesizerEventConsumer l8ClassSynthesizerEventConsumer) + CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer) throws ExecutionException { - new CfL8ClassSynthesizerCollection(appView, instructionDesugaring.getRetargetingInfo()) - .synthesizeClasses(executorService, l8ClassSynthesizerEventConsumer); + CfClassSynthesizerDesugaringCollection.create( + appView, instructionDesugaring.getRetargetingInfo()) + .synthesizeClasses(executorService, classSynthesizerEventConsumer); } private void postProcessingDesugaringForD8( D8MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException { D8CfPostProcessingDesugaringEventConsumer eventConsumer = - CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor); + CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor, instructionDesugaring); methodProcessor.newWave(); InterfaceMethodProcessorFacade interfaceDesugaring = instructionDesugaring.getInterfaceMethodPostProcessingDesugaring(ExcludeDexResources); @@ -495,26 +487,6 @@ DesugaredLibraryAPIConverter::generateTrackingWarnings); } - public void desugarClassesForD8( - List<DexProgramClass> classes, - D8CfClassDesugaringEventConsumer desugaringEventConsumer, - ExecutorService executorService) - throws ExecutionException { - if (classDesugaring.isEmpty()) { - return; - } - // Currently the classes can be processed in any order and do not require to be sorted. - ThreadUtils.processItems( - classes, clazz -> desugarClassForD8(clazz, desugaringEventConsumer), executorService); - } - - private void desugarClassForD8( - DexProgramClass clazz, D8CfClassDesugaringEventConsumer desugaringEventConsumer) { - if (classDesugaring.needsDesugaring(clazz)) { - classDesugaring.desugar(clazz, desugaringEventConsumer); - } - } - public void prepareDesugaringForD8(ExecutorService executorService) throws ExecutionException { // Prepare desugaring by collecting all the synthetic methods required on program classes. ProgramAdditions programAdditions = new ProgramAdditions(); @@ -686,7 +658,8 @@ printPhase("Primary optimization pass"); // Setup the argument propagator for the primary optimization pass. - appView.withArgumentPropagator(ArgumentPropagator::initializeCodeScanner); + appView.withArgumentPropagator( + argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing)); appView.withCallSiteOptimizationInfoPropagator( optimization -> { optimization.abandonCallSitePropagationForLambdaImplementationMethods( @@ -744,11 +717,9 @@ // Analyze the data collected by the argument propagator, use the analysis result to update // the parameter optimization infos, and rewrite the application. appView.withArgumentPropagator( - argumentPropagator -> { - argumentPropagator.populateParameterOptimizationInfo(executorService); - argumentPropagator.optimizeMethodParameters(); - argumentPropagator.enqueueMethodsForProcessing(postMethodProcessorBuilder); - }); + argumentPropagator -> + argumentPropagator.tearDownCodeScanner( + postMethodProcessorBuilder, executorService, timing)); if (libraryMethodOverrideAnalysis != null) { libraryMethodOverrideAnalysis.finish(); @@ -1618,7 +1589,7 @@ MutableMethodConversionOptions conversionOptions, Timing timing) { appView.withArgumentPropagator( - argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor)); + argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor, timing)); if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) { enumUnboxer.analyzeEnums(code, conversionOptions);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java deleted file mode 100644 index a885344..0000000 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.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; - -import com.android.tools.r8.graph.DexProgramClass; - -/** Interface for desugaring a class. */ -public interface CfClassDesugaring { - - void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer); - - /** Returns true if the given class needs desugaring. */ - boolean needsDesugaring(DexProgramClass clazz); -}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java deleted file mode 100644 index 1ddeb09..0000000 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java +++ /dev/null
@@ -1,89 +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; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.ir.desugar.records.RecordRewriter; -import com.google.common.collect.Iterables; -import java.util.ArrayList; -import java.util.List; - -/** Interface for desugaring a class. */ -public abstract class CfClassDesugaringCollection { - - public abstract void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer); - - /** Returns true if the given class needs desugaring. */ - public abstract boolean needsDesugaring(DexProgramClass clazz); - - public abstract boolean isEmpty(); - - public static CfClassDesugaringCollection empty() { - return EmptyCfClassDesugaringCollection.getInstance(); - } - - public static CfClassDesugaringCollection create(AppView<?> appView) { - List<CfClassDesugaring> desugarings = new ArrayList<>(); - RecordRewriter recordRewriter = RecordRewriter.create(appView); - if (recordRewriter != null) { - desugarings.add(recordRewriter); - } - if (desugarings.isEmpty()) { - return empty(); - } - return new NonEmptyCfClassDesugaringCollection(desugarings); - } - - public static class NonEmptyCfClassDesugaringCollection extends CfClassDesugaringCollection { - private final List<CfClassDesugaring> desugarings; - - NonEmptyCfClassDesugaringCollection(List<CfClassDesugaring> desugarings) { - this.desugarings = desugarings; - } - - @Override - public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) { - for (CfClassDesugaring desugaring : desugarings) { - desugaring.desugar(clazz, eventConsumer); - } - } - - @Override - public boolean needsDesugaring(DexProgramClass clazz) { - return Iterables.any(desugarings, desugaring -> desugaring.needsDesugaring(clazz)); - } - - @Override - public boolean isEmpty() { - return false; - } - } - - public static class EmptyCfClassDesugaringCollection extends CfClassDesugaringCollection { - - private static final EmptyCfClassDesugaringCollection INSTANCE = - new EmptyCfClassDesugaringCollection(); - - public static EmptyCfClassDesugaringCollection getInstance() { - return INSTANCE; - } - - @Override - public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) { - // Intentionally empty. - } - - @Override - public boolean needsDesugaring(DexProgramClass clazz) { - return false; - } - - @Override - public boolean isEmpty() { - return true; - } - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java deleted file mode 100644 index ef09aba..0000000 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java +++ /dev/null
@@ -1,38 +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; - -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.conversion.D8MethodProcessor; -import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer; - -public abstract class CfClassDesugaringEventConsumer implements RecordDesugaringEventConsumer { - - public static D8CfClassDesugaringEventConsumer createForD8(D8MethodProcessor methodProcessor) { - return new D8CfClassDesugaringEventConsumer(methodProcessor); - } - - public static class D8CfClassDesugaringEventConsumer extends CfClassDesugaringEventConsumer { - - private final D8MethodProcessor methodProcessor; - - public D8CfClassDesugaringEventConsumer(D8MethodProcessor methodProcessor) { - this.methodProcessor = methodProcessor; - } - - @Override - public void acceptRecordClass(DexProgramClass recordClass) { - methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods()); - } - - @Override - public void acceptRecordMethod(ProgramMethod method) { - assert false; - } - } - - // TODO(b/): Implement R8CfClassDesugaringEventConsumer -}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaring.java similarity index 66% rename from src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizer.java rename to src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaring.java index 2eefa63..38fa09a 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaring.java
@@ -4,7 +4,7 @@ package com.android.tools.r8.ir.desugar; -public interface CfL8ClassSynthesizer { +public interface CfClassSynthesizerDesugaring { - void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer); + void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java new file mode 100644 index 0000000..b0ea9ef --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
@@ -0,0 +1,80 @@ +// 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; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterL8Synthesizer; +import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizer; +import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo; +import com.android.tools.r8.ir.desugar.itf.ProgramEmulatedInterfaceSynthesizer; +import com.android.tools.r8.ir.desugar.records.RecordRewriter; +import com.android.tools.r8.utils.ThreadUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +public abstract class CfClassSynthesizerDesugaringCollection { + + public static CfClassSynthesizerDesugaringCollection create( + AppView<?> appView, RetargetingInfo retargetingInfo) { + Collection<CfClassSynthesizerDesugaring> synthesizers = new ArrayList<>(); + if (appView.options().isDesugaredLibraryCompilation()) { + ProgramEmulatedInterfaceSynthesizer emulatedInterfaceSynthesizer = + ProgramEmulatedInterfaceSynthesizer.create(appView); + if (emulatedInterfaceSynthesizer != null) { + synthesizers.add(emulatedInterfaceSynthesizer); + } + DesugaredLibraryRetargeterL8Synthesizer retargeterL8Synthesizer = + DesugaredLibraryRetargeterL8Synthesizer.create(appView, retargetingInfo); + if (retargeterL8Synthesizer != null) { + synthesizers.add(retargeterL8Synthesizer); + } + synthesizers.add(new DesugaredLibraryWrapperSynthesizer(appView)); + } + RecordRewriter recordRewriter = RecordRewriter.create(appView); + if (recordRewriter != null) { + synthesizers.add(recordRewriter); + } + if (synthesizers.isEmpty()) { + return new EmptyCfClassSynthesizerCollection(); + } + return new NonEmptyCfClassSynthesizerCollection(synthesizers); + } + + public abstract void synthesizeClasses( + ExecutorService executorService, CfClassSynthesizerDesugaringEventConsumer eventConsumer) + throws ExecutionException; + + static class NonEmptyCfClassSynthesizerCollection extends CfClassSynthesizerDesugaringCollection { + + private final Collection<CfClassSynthesizerDesugaring> synthesizers; + + public NonEmptyCfClassSynthesizerCollection( + Collection<CfClassSynthesizerDesugaring> synthesizers) { + assert !synthesizers.isEmpty(); + this.synthesizers = synthesizers; + } + + @Override + public void synthesizeClasses( + ExecutorService executorService, CfClassSynthesizerDesugaringEventConsumer eventConsumer) + throws ExecutionException { + ThreadUtils.processItems( + synthesizers, + synthesizer -> synthesizer.synthesizeClasses(eventConsumer), + executorService); + } + } + + static class EmptyCfClassSynthesizerCollection extends CfClassSynthesizerDesugaringCollection { + + @Override + public void synthesizeClasses( + ExecutorService executorService, CfClassSynthesizerDesugaringEventConsumer eventConsumer) + throws ExecutionException { + // Intentionally empty. + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java similarity index 70% rename from src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerEventConsumer.java rename to src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java index 5e41810..3f60ffe 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
@@ -7,19 +7,21 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer; -import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer; +import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer; +import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer; import com.google.common.collect.Sets; import java.util.Set; -public class CfL8ClassSynthesizerEventConsumer - implements EmulatedInterfaceSynthesizerEventConsumer, +public class CfClassSynthesizerDesugaringEventConsumer + implements L8ProgramEmulatedInterfaceSynthesizerEventConsumer, DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer, - DesugaredLibraryRetargeterL8SynthesizerEventConsumer { + DesugaredLibraryRetargeterL8SynthesizerEventConsumer, + RecordDesugaringEventConsumer { private Set<DexProgramClass> synthesizedClasses = Sets.newConcurrentHashSet(); @Override - public void acceptEmulatedInterface(DexProgramClass clazz) { + public void acceptProgramEmulatedInterface(DexProgramClass clazz) { synthesizedClasses.add(clazz); } @@ -33,8 +35,12 @@ synthesizedClasses.add(clazz); } + @Override + public void acceptRecordClass(DexProgramClass clazz) { + synthesizedClasses.add(clazz); + } + public Set<DexProgramClass> getSynthesizedClasses() { return synthesizedClasses; } - }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java index 6eda99c..4f6dd6c 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -18,11 +18,12 @@ import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer; import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo; import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.ClasspathEmulatedInterfaceSynthesizerEventConsumer; import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover; import com.android.tools.r8.ir.desugar.lambda.LambdaDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer; -import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceDesugaringEventConsumer; import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions; import com.google.common.collect.Sets; @@ -45,11 +46,12 @@ InvokeSpecialToSelfDesugaringEventConsumer, LambdaDesugaringEventConsumer, NestBasedAccessDesugaringEventConsumer, - RecordDesugaringEventConsumer, + RecordInstructionDesugaringEventConsumer, TwrCloseResourceDesugaringEventConsumer, InterfaceMethodDesugaringEventConsumer, DesugaredLibraryRetargeterInstructionEventConsumer, - DesugaredLibraryAPIConverterEventConsumer { + DesugaredLibraryAPIConverterEventConsumer, + ClasspathEmulatedInterfaceSynthesizerEventConsumer { public static D8CfInstructionDesugaringEventConsumer createForD8( D8MethodProcessor methodProcessor) { @@ -69,6 +71,11 @@ return new CfInstructionDesugaringEventConsumer() { @Override + public void acceptClasspathEmulatedInterface(DexClasspathClass clazz) { + assert false; + } + + @Override public void acceptWrapperClasspathClass(DexClasspathClass clazz) { assert false; } @@ -165,6 +172,11 @@ } @Override + public void acceptClasspathEmulatedInterface(DexClasspathClass clazz) { + // Intentionally empty. + } + + @Override public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) { methodProcessor.scheduleMethodForProcessing(backportedMethod, this); } @@ -326,19 +338,24 @@ } @Override + public void acceptClasspathEmulatedInterface(DexClasspathClass clazz) { + additions.addLiveClasspathClass(clazz); + } + + @Override public void acceptCompanionClassClinit(ProgramMethod method) { // TODO(b/183998768): Update this once desugaring is moved to the enqueuer. } @Override public void acceptRecordClass(DexProgramClass recordClass) { - // This is called each time an instruction or a class is found to require the record class. - assert false : "TODO(b/179146128): To be implemented"; + // Intentionally empty. The class will be hit by tracing if required. } @Override public void acceptRecordMethod(ProgramMethod method) { - assert false : "TODO(b/179146128): To be implemented"; + // Intentionally empty. The method will be hit by the tracing in R8 as if it was + // present in the input code, and thus nothing needs to be done. } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerCollection.java deleted file mode 100644 index 9accf26..0000000 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerCollection.java +++ /dev/null
@@ -1,43 +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; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterL8Synthesizer; -import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizer; -import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo; -import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizer; -import com.android.tools.r8.utils.ThreadUtils; -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; - -public class CfL8ClassSynthesizerCollection { - - private Collection<CfL8ClassSynthesizer> synthesizers = new ArrayList<>(); - - public CfL8ClassSynthesizerCollection(AppView<?> appView, RetargetingInfo retargetingInfo) { - assert appView.options().isDesugaredLibraryCompilation(); - EmulatedInterfaceSynthesizer emulatedInterfaceSynthesizer = - EmulatedInterfaceSynthesizer.create(appView); - if (emulatedInterfaceSynthesizer != null) { - synthesizers.add(emulatedInterfaceSynthesizer); - } - DesugaredLibraryRetargeterL8Synthesizer retargeterL8Synthesizer = - DesugaredLibraryRetargeterL8Synthesizer.create(appView, retargetingInfo); - if (retargeterL8Synthesizer != null) { - synthesizers.add(retargeterL8Synthesizer); - } - synthesizers.add(new DesugaredLibraryWrapperSynthesizer(appView)); - } - - public void synthesizeClasses( - ExecutorService executorService, CfL8ClassSynthesizerEventConsumer eventConsumer) - throws ExecutionException { - ThreadUtils.processItems( - synthesizers, synthesizer -> synthesizer.synthesizeClasses(eventConsumer), executorService); - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java index 2d99720..dbba296 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterPostProcessor; import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo; import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade; +import com.android.tools.r8.ir.desugar.records.RecordRewriter; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -69,6 +70,10 @@ if (apiCallbackSynthesizor != null) { desugarings.add(apiCallbackSynthesizor); } + RecordRewriter recordRewriter = RecordRewriter.create(appView); + if (recordRewriter != null) { + desugarings.add(recordRewriter); + } if (desugarings.isEmpty()) { return empty(); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java index 95407f8..66042a0 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -26,8 +26,8 @@ DesugaredLibraryAPICallbackSynthesizorEventConsumer { public static D8CfPostProcessingDesugaringEventConsumer createForD8( - D8MethodProcessor methodProcessor) { - return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor); + D8MethodProcessor methodProcessor, CfInstructionDesugaringCollection instructionDesugaring) { + return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, instructionDesugaring); } public static R8PostProcessingDesugaringEventConsumer createForR8(SyntheticAdditions additions) { @@ -43,9 +43,21 @@ // Methods cannot be processed directly because we cannot add method to classes while // concurrently processing other methods. private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent(); + private final CfInstructionDesugaringCollection instructionDesugaring; - private D8CfPostProcessingDesugaringEventConsumer(D8MethodProcessor methodProcessor) { + private D8CfPostProcessingDesugaringEventConsumer( + D8MethodProcessor methodProcessor, + CfInstructionDesugaringCollection instructionDesugaring) { this.methodProcessor = methodProcessor; + this.instructionDesugaring = instructionDesugaring; + } + + private void addMethodToReprocess(ProgramMethod method) { + if (instructionDesugaring.needsDesugaring(method)) { + instructionDesugaring.needsDesugaring(method); + } + assert !instructionDesugaring.needsDesugaring(method); + methodsToReprocess.add(method); } @Override @@ -60,7 +72,7 @@ @Override public void acceptForwardingMethod(ProgramMethod method) { - methodsToReprocess.add(method); + addMethodToReprocess(method); } @Override @@ -78,7 +90,7 @@ @Override public void acceptAPIConversionCallback(ProgramMethod method) { - methodsToReprocess.add(method); + addMethodToReprocess(method); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java index c4ce73e..b117828 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -114,7 +114,6 @@ } this.recordRewriter = RecordRewriter.create(appView); if (recordRewriter != null) { - assert !appView.enableWholeProgramOptimizations() : "To be implemented"; desugarings.add(recordRewriter); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterL8Synthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterL8Synthesizer.java index dad4cd6..73cbd3d 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterL8Synthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterL8Synthesizer.java
@@ -5,11 +5,11 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizer; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; import com.android.tools.r8.utils.collections.DexClassAndMethodSet; -public class DesugaredLibraryRetargeterL8Synthesizer implements CfL8ClassSynthesizer { +public class DesugaredLibraryRetargeterL8Synthesizer implements CfClassSynthesizerDesugaring { private final AppView<?> appView; private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper; @@ -33,7 +33,7 @@ } @Override - public void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer) { + public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) { assert !emulatedDispatchMethods.isEmpty(); for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) { syntheticHelper.ensureProgramEmulatedHolderDispatchMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java index 3e21feb..aa69845 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
@@ -25,8 +25,8 @@ import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizer; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer; import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider; @@ -93,7 +93,7 @@ // return new j$....BiFunction$-WRP(wrappedValue); // } // } -public class DesugaredLibraryWrapperSynthesizer implements CfL8ClassSynthesizer { +public class DesugaredLibraryWrapperSynthesizer implements CfClassSynthesizerDesugaring { private final AppView<?> appView; private final DexItemFactory factory; @@ -633,7 +633,7 @@ // the wrappers with the conversion methods only, then the virtual methods assuming the // conversion methods are present. @Override - public void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer) { + public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) { DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration; List<DexProgramClass> validClassesToWrap = new ArrayList<>(); for (DexType type : conf.getWrapperConversions()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizerEventConsumer.java index cb73851..ee8f0d8 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizerEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizerEventConsumer.java
@@ -4,9 +4,16 @@ package com.android.tools.r8.ir.desugar.itf; +import com.android.tools.r8.graph.DexClasspathClass; import com.android.tools.r8.graph.DexProgramClass; public interface EmulatedInterfaceSynthesizerEventConsumer { - void acceptEmulatedInterface(DexProgramClass clazz); + interface L8ProgramEmulatedInterfaceSynthesizerEventConsumer { + void acceptProgramEmulatedInterface(DexProgramClass clazz); + } + + interface ClasspathEmulatedInterfaceSynthesizerEventConsumer { + void acceptClasspathEmulatedInterface(DexClasspathClass clazz); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java index 65816d4..92a64fe 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -32,6 +32,7 @@ import com.android.tools.r8.graph.InvalidCode; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.ClasspathEmulatedInterfaceSynthesizerEventConsumer; import com.android.tools.r8.synthesis.SyntheticMethodBuilder; import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; import com.android.tools.r8.utils.InternalOptions; @@ -127,8 +128,8 @@ return true; } - public static DexMethod emulateInterfaceLibraryMethod( - DexClassAndMethod method, DexItemFactory factory) { + public DexMethod emulateInterfaceLibraryMethod(DexClassAndMethod method) { + DexItemFactory factory = appView.dexItemFactory(); return factory.createMethod( getEmulateLibraryInterfaceClassType(method.getHolderType(), factory), factory.prependTypeToProto(method.getHolderType(), method.getProto()), @@ -184,6 +185,42 @@ return factory.createType(interfaceTypeDescriptor); } + DexClassAndMethod ensureEmulatedInterfaceMethod( + DexClassAndMethod method, ClasspathEmulatedInterfaceSynthesizerEventConsumer eventConsumer) { + DexMethod emulatedInterfaceMethod = emulateInterfaceLibraryMethod(method); + if (method.isProgramMethod()) { + assert appView.options().isDesugaredLibraryCompilation(); + DexProgramClass emulatedInterface = + appView + .getSyntheticItems() + .getExistingFixedClass( + SyntheticKind.EMULATED_INTERFACE_CLASS, + method.asProgramMethod().getHolder(), + appView); + return emulatedInterface.lookupProgramMethod(emulatedInterfaceMethod); + } + return appView + .getSyntheticItems() + .ensureFixedClasspathClassMethod( + emulatedInterfaceMethod.getName(), + emulatedInterfaceMethod.getProto(), + SyntheticKind.EMULATED_INTERFACE_CLASS, + method.getHolder().asClasspathOrLibraryClass(), + appView, + classBuilder -> {}, + clazz -> { + // TODO(b/183998768): When interface method desugaring is cf to cf in R8, the + // eventConsumer should always be non null. + if (eventConsumer != null) { + eventConsumer.acceptClasspathEmulatedInterface(clazz); + } + }, + methodBuilder -> + methodBuilder + .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) + .setCode(DexEncodedMethod::buildEmptyThrowingCfCode)); + } + DexClassAndMethod ensureDefaultAsMethodOfCompanionClassStub(DexClassAndMethod method) { if (method.isProgramMethod()) { return ensureDefaultAsMethodOfProgramCompanionClassStub(method.asProgramMethod()); @@ -323,6 +360,7 @@ context, appView, classBuilder -> {}, + ignored -> {}, methodBuilder -> methodBuilder .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java index dfdfb45..8b3f70e 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -447,7 +447,7 @@ return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke); } return rewriteInvokeInterfaceOrInvokeVirtual( - invoke.getMethod(), invoke.isInterface(), rewriteInvoke); + invoke.getMethod(), invoke.isInterface(), rewriteInvoke, eventConsumer); } if (invoke.isInvokeStatic()) { Consumer<ProgramMethod> staticOutliningMethodConsumer = @@ -643,7 +643,7 @@ rewriteInvokeDirect(invoke.getInvokedMethod(), context, rewriteInvoke); } else if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) { rewriteInvokeInterfaceOrInvokeVirtual( - invoke.getInvokedMethod(), invoke.getInterfaceBit(), rewriteInvoke); + invoke.getInvokedMethod(), invoke.getInterfaceBit(), rewriteInvoke, null); } else { Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow = (resolutionResult) -> @@ -971,7 +971,7 @@ .asSingleResolution(); if (resolution != null && (resolution.getResolvedHolder().isLibraryClass() - || appView.options().isDesugaredLibraryCompilation())) { + || helper.isEmulatedInterface(resolution.getResolvedHolder().type))) { DexClassAndMethod defaultMethod = appView.definitionFor(emulatedItf).lookupClassMethod(invokedMethod); if (defaultMethod != null && !helper.dontRewrite(defaultMethod)) { @@ -985,12 +985,13 @@ private Collection<CfInstruction> rewriteInvokeInterfaceOrInvokeVirtual( DexMethod invokedMethod, boolean interfaceBit, - Function<DexMethod, Collection<CfInstruction>> rewriteInvoke) { + Function<DexMethod, Collection<CfInstruction>> rewriteInvoke, + CfInstructionDesugaringEventConsumer eventConsumer) { DexClassAndMethod defaultMethod = defaultMethodForEmulatedDispatchOrNull(invokedMethod, interfaceBit); if (defaultMethod != null) { return rewriteInvoke.apply( - InterfaceDesugaringSyntheticHelper.emulateInterfaceLibraryMethod(defaultMethod, factory)); + helper.ensureEmulatedInterfaceMethod(defaultMethod, eventConsumer).getReference()); } return null; }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java index 1437d48..ac12741 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.ir.desugar.itf; - import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.code.Instruction;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java similarity index 78% rename from src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizer.java rename to src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java index 5af4f7e..d68dec8 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.desugar.itf; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; @@ -12,11 +13,13 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizer; -import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer; import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider; 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; @@ -30,21 +33,21 @@ import java.util.Map; import java.util.Set; -public final class EmulatedInterfaceSynthesizer implements CfL8ClassSynthesizer { +public final class ProgramEmulatedInterfaceSynthesizer implements CfClassSynthesizerDesugaring { private final AppView<?> appView; private final InterfaceDesugaringSyntheticHelper helper; private final Map<DexType, List<DexType>> emulatedInterfacesHierarchy; - public static EmulatedInterfaceSynthesizer create(AppView<?> appView) { + public static ProgramEmulatedInterfaceSynthesizer create(AppView<?> appView) { if (!appView.options().isDesugaredLibraryCompilation() || appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()) { return null; } - return new EmulatedInterfaceSynthesizer(appView); + return new ProgramEmulatedInterfaceSynthesizer(appView); } - EmulatedInterfaceSynthesizer(AppView<?> appView) { + public ProgramEmulatedInterfaceSynthesizer(AppView<?> appView) { this.appView = appView; helper = new InterfaceDesugaringSyntheticHelper(appView); // Avoid the computation outside L8 since it is not needed. @@ -92,35 +95,48 @@ } } - DexProgramClass ensureEmulateInterfaceLibrary( - DexProgramClass emulatedInterface, EmulatedInterfaceSynthesizerEventConsumer eventConsumer) { + DexProgramClass synthesizeProgramEmulatedInterface( + DexProgramClass emulatedInterface, + L8ProgramEmulatedInterfaceSynthesizerEventConsumer eventConsumer) { + return appView + .getSyntheticItems() + .ensureFixedClass( + SyntheticNaming.SyntheticKind.EMULATED_INTERFACE_CLASS, + emulatedInterface, + appView, + builder -> synthesizeEmulateInterfaceMethods(emulatedInterface, builder), + eventConsumer::acceptProgramEmulatedInterface); + } + + private void synthesizeEmulateInterfaceMethods( + DexProgramClass emulatedInterface, SyntheticProgramClassBuilder builder) { assert helper.isEmulatedInterface(emulatedInterface.type); - DexProgramClass emulateInterfaceClass = - appView - .getSyntheticItems() - .ensureFixedClass( - SyntheticNaming.SyntheticKind.EMULATED_INTERFACE_CLASS, - emulatedInterface, - appView, - builder -> - emulatedInterface.forEachProgramMethodMatching( - DexEncodedMethod::isDefaultMethod, - method -> - builder.addMethod( - methodBuilder -> - synthesizeEmulatedInterfaceMethod( - method, emulatedInterface, methodBuilder))), - ignored -> {}); - eventConsumer.acceptEmulatedInterface(emulateInterfaceClass); - assert emulateInterfaceClass.getType() + emulatedInterface.forEachProgramVirtualMethodMatching( + DexEncodedMethod::isDefaultMethod, + method -> + builder.addMethod( + methodBuilder -> + synthesizeEmulatedInterfaceMethod(method, emulatedInterface, methodBuilder))); + assert builder.getType() == InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType( emulatedInterface.type, appView.dexItemFactory()); - return emulateInterfaceClass; } private void synthesizeEmulatedInterfaceMethod( ProgramMethod method, DexProgramClass theInterface, SyntheticMethodBuilder methodBuilder) { assert !method.getDefinition().isStatic(); + DexMethod emulatedMethod = helper.emulateInterfaceLibraryMethod(method); + methodBuilder + .setName(emulatedMethod.getName()) + .setProto(emulatedMethod.getProto()) + .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) + .setCode( + emulatedInterfaceMethod -> + synthesizeCfCode(method.asProgramMethod(), theInterface, emulatedInterfaceMethod)); + } + + private CfCode synthesizeCfCode( + ProgramMethod method, DexProgramClass theInterface, DexMethod emulatedInterfaceMethod) { DexMethod libraryMethod = method .getReference() @@ -129,23 +145,14 @@ helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference(); List<Pair<DexType, DexMethod>> extraDispatchCases = getDispatchCases(method, theInterface, companionMethod); - DexMethod emulatedMethod = - InterfaceDesugaringSyntheticHelper.emulateInterfaceLibraryMethod( - method, appView.dexItemFactory()); - methodBuilder - .setName(emulatedMethod.getName()) - .setProto(emulatedMethod.getProto()) - .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) - .setCode( - emulatedInterfaceMethod -> - new EmulateInterfaceSyntheticCfCodeProvider( - emulatedInterfaceMethod.getHolderType(), - method.getHolderType(), - companionMethod, - libraryMethod, - extraDispatchCases, - appView) - .generateCfCode()); + return new EmulateInterfaceSyntheticCfCodeProvider( + emulatedInterfaceMethod.getHolderType(), + method.getHolderType(), + companionMethod, + libraryMethod, + extraDispatchCases, + appView) + .generateCfCode(); } private List<Pair<DexType, DexMethod>> getDispatchCases( @@ -219,7 +226,7 @@ } @Override - public void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer) { + public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) { assert appView.options().isDesugaredLibraryCompilation(); for (DexType emulatedInterfaceType : helper.getEmulatedInterfaces()) { DexClass emulatedInterfaceClazz = appView.definitionFor(emulatedInterfaceType); @@ -231,12 +238,12 @@ assert emulatedInterface != null; if (!appView.isAlreadyLibraryDesugared(emulatedInterface) && needsEmulateInterfaceLibrary(emulatedInterface)) { - ensureEmulateInterfaceLibrary(emulatedInterface, eventConsumer); + synthesizeProgramEmulatedInterface(emulatedInterface, eventConsumer); } } } - private boolean needsEmulateInterfaceLibrary(DexProgramClass emulatedInterface) { + private boolean needsEmulateInterfaceLibrary(DexClass emulatedInterface) { return Iterables.any(emulatedInterface.methods(), DexEncodedMethod::isDefaultMethod); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java index 0de85b5..adab36a 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
@@ -11,7 +11,6 @@ import com.android.tools.r8.cf.code.CfArithmeticBinop; import com.android.tools.r8.cf.code.CfArrayLength; import com.android.tools.r8.cf.code.CfArrayLoad; -import com.android.tools.r8.cf.code.CfCheckCast; import com.android.tools.r8.cf.code.CfConstNumber; import com.android.tools.r8.cf.code.CfConstString; import com.android.tools.r8.cf.code.CfFrame; @@ -44,123 +43,22 @@ public final class RecordCfMethods { public static void registerSynthesizedCodeReferences(DexItemFactory factory) { - factory.createSynthesizedType("Ljava/lang/Record;"); factory.createSynthesizedType("Ljava/util/Arrays;"); factory.createSynthesizedType("[Ljava/lang/Object;"); factory.createSynthesizedType("[Ljava/lang/String;"); } - public static CfCode RecordMethods_equals(InternalOptions options, DexMethod method) { - CfLabel label0 = new CfLabel(); - CfLabel label1 = new CfLabel(); - CfLabel label2 = new CfLabel(); - CfLabel label3 = new CfLabel(); - CfLabel label4 = new CfLabel(); - CfLabel label5 = new CfLabel(); - return new CfCode( - method.holder, - 2, - 2, - ImmutableList.of( - label0, - new CfLoad(ValueType.OBJECT, 0), - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.objectType, - options.itemFactory.createProto(options.itemFactory.classType), - options.itemFactory.createString("getClass")), - false), - new CfLoad(ValueType.OBJECT, 1), - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.objectType, - options.itemFactory.createProto(options.itemFactory.classType), - options.itemFactory.createString("getClass")), - false), - new CfIfCmp(If.Type.NE, ValueType.OBJECT, label3), - new CfLoad(ValueType.OBJECT, 1), - new CfCheckCast(options.itemFactory.createType("Ljava/lang/Record;")), - label1, - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/lang/Record;"), - options.itemFactory.createProto( - options.itemFactory.createType("[Ljava/lang/Object;")), - options.itemFactory.createString("$record$getFieldsAsObjects")), - false), - new CfLoad(ValueType.OBJECT, 0), - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/lang/Record;"), - options.itemFactory.createProto( - options.itemFactory.createType("[Ljava/lang/Object;")), - options.itemFactory.createString("$record$getFieldsAsObjects")), - false), - label2, - new CfInvoke( - 184, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/util/Arrays;"), - options.itemFactory.createProto( - options.itemFactory.booleanType, - options.itemFactory.createType("[Ljava/lang/Object;"), - options.itemFactory.createType("[Ljava/lang/Object;")), - options.itemFactory.createString("equals")), - false), - new CfIf(If.Type.EQ, ValueType.INT, label3), - new CfConstNumber(1, ValueType.INT), - new CfGoto(label4), - label3, - new CfFrame( - new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1}, - new FrameType[] { - FrameType.initialized(options.itemFactory.createType("Ljava/lang/Record;")), - FrameType.initialized(options.itemFactory.objectType) - }), - new ArrayDeque<>(Arrays.asList())), - new CfConstNumber(0, ValueType.INT), - label4, - new CfFrame( - new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1}, - new FrameType[] { - FrameType.initialized(options.itemFactory.createType("Ljava/lang/Record;")), - FrameType.initialized(options.itemFactory.objectType) - }), - new ArrayDeque<>( - Arrays.asList(FrameType.initialized(options.itemFactory.intType)))), - new CfReturn(ValueType.INT), - label5), - ImmutableList.of(), - ImmutableList.of()); - } - public static CfCode RecordMethods_hashCode(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); - CfLabel label2 = new CfLabel(); - CfLabel label3 = new CfLabel(); return new CfCode( method.holder, 2, - 1, + 2, ImmutableList.of( label0, new CfConstNumber(31, ValueType.INT), - new CfLoad(ValueType.OBJECT, 0), - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/lang/Record;"), - options.itemFactory.createProto( - options.itemFactory.createType("[Ljava/lang/Object;")), - options.itemFactory.createString("$record$getFieldsAsObjects")), - false), + new CfLoad(ValueType.OBJECT, 1), new CfInvoke( 184, options.itemFactory.createMethod( @@ -172,14 +70,6 @@ false), new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT), new CfLoad(ValueType.OBJECT, 0), - label1, - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.objectType, - options.itemFactory.createProto(options.itemFactory.classType), - options.itemFactory.createString("getClass")), - false), new CfInvoke( 182, options.itemFactory.createMethod( @@ -188,9 +78,8 @@ options.itemFactory.createString("hashCode")), false), new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), - label2, new CfReturn(ValueType.INT), - label3), + label1), ImmutableList.of(), ImmutableList.of()); } @@ -210,11 +99,10 @@ CfLabel label11 = new CfLabel(); CfLabel label12 = new CfLabel(); CfLabel label13 = new CfLabel(); - CfLabel label14 = new CfLabel(); return new CfCode( method.holder, 3, - 7, + 6, ImmutableList.of( label0, new CfLoad(ValueType.OBJECT, 2), @@ -234,7 +122,7 @@ new Int2ReferenceAVLTreeMap<>( new int[] {0, 1, 2}, new FrameType[] { - FrameType.initialized(options.itemFactory.createType("Ljava/lang/Record;")), + FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")), FrameType.initialized(options.itemFactory.stringType), FrameType.initialized(options.itemFactory.stringType) }), @@ -255,7 +143,7 @@ new Int2ReferenceAVLTreeMap<>( new int[] {0, 1, 2}, new FrameType[] { - FrameType.initialized(options.itemFactory.createType("Ljava/lang/Record;")), + FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")), FrameType.initialized(options.itemFactory.stringType), FrameType.initialized(options.itemFactory.stringType) }), @@ -265,17 +153,6 @@ options.itemFactory.createType("[Ljava/lang/String;"))))), new CfStore(ValueType.OBJECT, 3), label3, - new CfLoad(ValueType.OBJECT, 0), - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/lang/Record;"), - options.itemFactory.createProto( - options.itemFactory.createType("[Ljava/lang/Object;")), - options.itemFactory.createString("$record$getFieldsAsObjects")), - false), - new CfStore(ValueType.OBJECT, 4), - label4, new CfNew(options.itemFactory.stringBuilderType), new CfStackInstruction(CfStackInstruction.Opcode.Dup), new CfInvoke( @@ -285,9 +162,9 @@ options.itemFactory.createProto(options.itemFactory.voidType), options.itemFactory.createString("<init>")), false), - new CfStore(ValueType.OBJECT, 5), - label5, - new CfLoad(ValueType.OBJECT, 5), + new CfStore(ValueType.OBJECT, 4), + label4, + new CfLoad(ValueType.OBJECT, 4), new CfLoad(ValueType.OBJECT, 1), new CfInvoke( 182, @@ -307,31 +184,30 @@ options.itemFactory.createString("append")), false), new CfStackInstruction(CfStackInstruction.Opcode.Pop), - label6, + label5, new CfConstNumber(0, ValueType.INT), - new CfStore(ValueType.INT, 6), - label7, + new CfStore(ValueType.INT, 5), + label6, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3, 4, 5, 6}, + new int[] {0, 1, 2, 3, 4, 5}, new FrameType[] { - FrameType.initialized(options.itemFactory.createType("Ljava/lang/Record;")), + FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")), FrameType.initialized(options.itemFactory.stringType), FrameType.initialized(options.itemFactory.stringType), FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")), - FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")), FrameType.initialized(options.itemFactory.stringBuilderType), FrameType.initialized(options.itemFactory.intType) }), new ArrayDeque<>(Arrays.asList())), - new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 5), new CfLoad(ValueType.OBJECT, 3), new CfArrayLength(), - new CfIfCmp(If.Type.GE, ValueType.INT, label12), - label8, - new CfLoad(ValueType.OBJECT, 5), + new CfIfCmp(If.Type.GE, ValueType.INT, label11), + label7, + new CfLoad(ValueType.OBJECT, 4), new CfLoad(ValueType.OBJECT, 3), - new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 5), new CfArrayLoad(MemberType.OBJECT), new CfInvoke( 182, @@ -350,8 +226,8 @@ options.itemFactory.stringBuilderType, options.itemFactory.stringType), options.itemFactory.createString("append")), false), - new CfLoad(ValueType.OBJECT, 4), - new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 5), new CfArrayLoad(MemberType.OBJECT), new CfInvoke( 182, @@ -362,15 +238,15 @@ options.itemFactory.createString("append")), false), new CfStackInstruction(CfStackInstruction.Opcode.Pop), - label9, - new CfLoad(ValueType.INT, 6), + label8, + new CfLoad(ValueType.INT, 5), new CfLoad(ValueType.OBJECT, 3), new CfArrayLength(), new CfConstNumber(1, ValueType.INT), new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), - new CfIfCmp(If.Type.EQ, ValueType.INT, label11), - label10, - new CfLoad(ValueType.OBJECT, 5), + new CfIfCmp(If.Type.EQ, ValueType.INT, label10), + label9, + new CfLoad(ValueType.OBJECT, 4), new CfConstString(options.itemFactory.createString(", ")), new CfInvoke( 182, @@ -381,36 +257,34 @@ options.itemFactory.createString("append")), false), new CfStackInstruction(CfStackInstruction.Opcode.Pop), - label11, - new CfFrame( - new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3, 4, 5, 6}, - new FrameType[] { - FrameType.initialized(options.itemFactory.createType("Ljava/lang/Record;")), - FrameType.initialized(options.itemFactory.stringType), - FrameType.initialized(options.itemFactory.stringType), - FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")), - FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")), - FrameType.initialized(options.itemFactory.stringBuilderType), - FrameType.initialized(options.itemFactory.intType) - }), - new ArrayDeque<>(Arrays.asList())), - new CfIinc(6, 1), - new CfGoto(label7), - label12, + label10, new CfFrame( new Int2ReferenceAVLTreeMap<>( new int[] {0, 1, 2, 3, 4, 5}, new FrameType[] { - FrameType.initialized(options.itemFactory.createType("Ljava/lang/Record;")), + FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")), FrameType.initialized(options.itemFactory.stringType), FrameType.initialized(options.itemFactory.stringType), FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")), + FrameType.initialized(options.itemFactory.stringBuilderType), + FrameType.initialized(options.itemFactory.intType) + }), + new ArrayDeque<>(Arrays.asList())), + new CfIinc(5, 1), + new CfGoto(label6), + label11, + new CfFrame( + new Int2ReferenceAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4}, + new FrameType[] { FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")), + FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")), FrameType.initialized(options.itemFactory.stringBuilderType) }), new ArrayDeque<>(Arrays.asList())), - new CfLoad(ValueType.OBJECT, 5), + new CfLoad(ValueType.OBJECT, 4), new CfConstString(options.itemFactory.createString("]")), new CfInvoke( 182, @@ -421,8 +295,8 @@ options.itemFactory.createString("append")), false), new CfStackInstruction(CfStackInstruction.Opcode.Pop), - label13, - new CfLoad(ValueType.OBJECT, 5), + label12, + new CfLoad(ValueType.OBJECT, 4), new CfInvoke( 182, options.itemFactory.createMethod( @@ -431,7 +305,7 @@ options.itemFactory.createString("toString")), false), new CfReturn(ValueType.OBJECT), - label14), + label13), ImmutableList.of(), ImmutableList.of()); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java index 5affc2b..f81b5d6 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java
@@ -1,7 +1,6 @@ // 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.records; import com.android.tools.r8.graph.DexProgramClass; @@ -11,5 +10,8 @@ void acceptRecordClass(DexProgramClass recordClass); - void acceptRecordMethod(ProgramMethod method); + interface RecordInstructionDesugaringEventConsumer extends RecordDesugaringEventConsumer { + + void acceptRecordMethod(ProgramMethod method); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java index 5333665..39a6ae1 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -4,11 +4,15 @@ package com.android.tools.r8.ir.desugar.records; +import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Dup; +import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Swap; + import com.android.tools.r8.cf.code.CfConstString; import com.android.tools.r8.cf.code.CfFieldInstruction; import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.cf.code.CfInvokeDynamic; +import com.android.tools.r8.cf.code.CfStackInstruction; import com.android.tools.r8.cf.code.CfTypeInstruction; import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.dex.Constants; @@ -35,14 +39,20 @@ import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.desugar.CfClassDesugaring; -import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring; +import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaring; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring; +import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.FreshLocalProvider; import com.android.tools.r8.ir.desugar.LocalStackAllocator; +import com.android.tools.r8.ir.desugar.ProgramAdditions; +import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider; -import com.android.tools.r8.ir.synthetic.RecordGetFieldsAsObjectsCfCodeProvider; +import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider; +import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider; +import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider; import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo; import com.android.tools.r8.synthesis.SyntheticNaming; import com.android.tools.r8.utils.InternalOptions; @@ -51,18 +61,21 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.function.BiFunction; import org.objectweb.asm.Opcodes; -public class RecordRewriter implements CfInstructionDesugaring, CfClassDesugaring { +public class RecordRewriter + implements CfInstructionDesugaring, CfClassSynthesizerDesugaring, CfPostProcessingDesugaring { private final AppView<?> appView; private final DexItemFactory factory; private final DexProto recordToStringHelperProto; - private final DexProto recordEqualsHelperProto; private final DexProto recordHashCodeHelperProto; public static final String GET_FIELDS_AS_OBJECTS_METHOD_NAME = "$record$getFieldsAsObjects"; + public static final String EQUALS_RECORD_METHOD_NAME = "$record$equals"; public static RecordRewriter create(AppView<?> appView) { return appView.options().shouldDesugarRecords() ? new RecordRewriter(appView) : null; @@ -71,6 +84,7 @@ public static void registerSynthesizedCodeReferences(DexItemFactory factory) { RecordCfMethods.registerSynthesizedCodeReferences(factory); RecordGetFieldsAsObjectsCfCodeProvider.registerSynthesizedCodeReferences(factory); + RecordEqualsCfCodeProvider.registerSynthesizedCodeReferences(factory); } private RecordRewriter(AppView<?> appView) { @@ -78,10 +92,34 @@ factory = appView.dexItemFactory(); recordToStringHelperProto = factory.createProto( - factory.stringType, factory.recordType, factory.stringType, factory.stringType); - recordEqualsHelperProto = - factory.createProto(factory.booleanType, factory.recordType, factory.objectType); - recordHashCodeHelperProto = factory.createProto(factory.intType, factory.recordType); + factory.stringType, factory.objectArrayType, factory.stringType, factory.stringType); + recordHashCodeHelperProto = + factory.createProto(factory.intType, factory.classType, factory.objectArrayType); + } + + @Override + public void prepare(ProgramMethod method, ProgramAdditions programAdditions) { + CfCode cfCode = method.getDefinition().getCode().asCfCode(); + for (CfInstruction instruction : cfCode.getInstructions()) { + if (instruction.isInvokeDynamic() && needsDesugaring(instruction, method)) { + prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), method, programAdditions); + } + } + } + + private void prepareInvokeDynamicOnRecord( + CfInvokeDynamic invokeDynamic, ProgramMethod context, ProgramAdditions programAdditions) { + RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context); + if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName + || recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) { + ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions); + return; + } + if (recordInvokeDynamic.getMethodName() == factory.equalsMethodName) { + ensureEqualsRecord(recordInvokeDynamic, programAdditions); + return; + } + throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared."); } @Override @@ -103,21 +141,21 @@ if (instruction.isInvoke()) { CfInvoke cfInvoke = instruction.asInvoke(); if (refersToRecord(cfInvoke.getMethod())) { - requiresRecordClass(eventConsumer); + ensureRecordClass(eventConsumer); } return; } if (instruction.isFieldInstruction()) { CfFieldInstruction fieldInstruction = instruction.asFieldInstruction(); if (refersToRecord(fieldInstruction.getField())) { - requiresRecordClass(eventConsumer); + ensureRecordClass(eventConsumer); } return; } if (instruction.isTypeInstruction()) { CfTypeInstruction typeInstruction = instruction.asTypeInstruction(); if (refersToRecord(typeInstruction.getType())) { - requiresRecordClass(eventConsumer); + ensureRecordClass(eventConsumer); } return; } @@ -134,7 +172,10 @@ MethodProcessingContext methodProcessingContext, DexItemFactory dexItemFactory) { assert !instruction.isInitClass(); - if (instruction.isInvokeDynamic() && needsDesugaring(instruction.asInvokeDynamic(), context)) { + if (!needsDesugaring(instruction, context)) { + return null; + } + if (instruction.isInvokeDynamic()) { return desugarInvokeDynamicOnRecord( instruction.asInvokeDynamic(), localStackAllocator, @@ -142,24 +183,52 @@ eventConsumer, methodProcessingContext); } - if (instruction.isInvoke()) { - CfInvoke cfInvoke = instruction.asInvoke(); - DexMethod newMethod = - rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType())); - if (newMethod != cfInvoke.getMethod()) { - return Collections.singletonList( - new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface())); - } - } - return null; + assert instruction.isInvoke(); + CfInvoke cfInvoke = instruction.asInvoke(); + DexMethod newMethod = + rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType())); + assert newMethod != cfInvoke.getMethod(); + return Collections.singletonList( + new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface())); } - public List<CfInstruction> desugarInvokeDynamicOnRecord( - CfInvokeDynamic invokeDynamic, - LocalStackAllocator localStackAllocator, - ProgramMethod context, - CfInstructionDesugaringEventConsumer eventConsumer, - MethodProcessingContext methodProcessingContext) { + static class RecordInvokeDynamic { + + private final DexString methodName; + private final DexString fieldNames; + private final DexField[] fields; + private final DexProgramClass recordClass; + + private RecordInvokeDynamic( + DexString methodName, + DexString fieldNames, + DexField[] fields, + DexProgramClass recordClass) { + this.methodName = methodName; + this.fieldNames = fieldNames; + this.fields = fields; + this.recordClass = recordClass; + } + + DexField[] getFields() { + return fields; + } + + DexProgramClass getRecordClass() { + return recordClass; + } + + DexString getFieldNames() { + return fieldNames; + } + + DexString getMethodName() { + return methodName; + } + } + + private RecordInvokeDynamic parseInvokeDynamicOnRecord( + CfInvokeDynamic invokeDynamic, ProgramMethod context) { assert needsDesugaring(invokeDynamic, context); DexCallSite callSite = invokeDynamic.getCallSite(); DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType(); @@ -172,34 +241,53 @@ } DexProgramClass recordClass = appView.definitionFor(recordValueType.getValue()).asProgramClass(); - if (callSite.methodName == factory.toStringMethodName) { - DexString simpleName = - ClassNameComputationInfo.ClassNameMapping.SIMPLE_NAME.map( - recordValueType.getValue().toDescriptorString(), context.getHolder(), factory); + return new RecordInvokeDynamic(callSite.methodName, fieldNames, fields, recordClass); + } + + private List<CfInstruction> desugarInvokeDynamicOnRecord( + CfInvokeDynamic invokeDynamic, + LocalStackAllocator localStackAllocator, + ProgramMethod context, + CfInstructionDesugaringEventConsumer eventConsumer, + MethodProcessingContext methodProcessingContext) { + RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context); + if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName) { return desugarInvokeRecordToString( - recordClass, - fieldNames, - fields, - simpleName, + recordInvokeDynamic, localStackAllocator, + context, eventConsumer, methodProcessingContext); } - if (callSite.methodName == factory.hashCodeMethodName) { + if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) { return desugarInvokeRecordHashCode( - recordClass, fields, eventConsumer, methodProcessingContext); + recordInvokeDynamic, localStackAllocator, eventConsumer, methodProcessingContext); } - if (callSite.methodName == factory.equalsMethodName) { - return desugarInvokeRecordEquals(recordClass, fields, eventConsumer, methodProcessingContext); + if (recordInvokeDynamic.getMethodName() == factory.equalsMethodName) { + return desugarInvokeRecordEquals(recordInvokeDynamic); } throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared."); } + private ProgramMethod synthesizeEqualsRecordMethod( + DexProgramClass clazz, DexMethod getFieldsAsObjects, DexMethod method) { + return synthesizeMethod( + clazz, new RecordEqualsCfCodeProvider(appView, clazz.type, getFieldsAsObjects), method); + } + private ProgramMethod synthesizeGetFieldsAsObjectsMethod( DexProgramClass clazz, DexField[] fields, DexMethod method) { + return synthesizeMethod( + clazz, + new RecordGetFieldsAsObjectsCfCodeProvider(appView, factory.recordTagType, fields), + method); + } + + private ProgramMethod synthesizeMethod( + DexProgramClass clazz, SyntheticCfCodeProvider provider, DexMethod method) { MethodAccessFlags methodAccessFlags = MethodAccessFlags.fromSharedAccessFlags( - Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false); + Constants.ACC_SYNTHETIC | Constants.ACC_PRIVATE, false); DexEncodedMethod encodedMethod = new DexEncodedMethod( method, @@ -209,26 +297,30 @@ ParameterAnnotationsList.empty(), null, true); - encodedMethod.setCode( - new RecordGetFieldsAsObjectsCfCodeProvider(appView, factory.recordTagType, fields) - .generateCfCode(), - appView); + encodedMethod.setCode(provider.generateCfCode(), appView); return new ProgramMethod(clazz, encodedMethod); } - private void ensureGetFieldsAsObjects( - DexProgramClass clazz, DexField[] fields, RecordDesugaringEventConsumer eventConsumer) { + private DexMethod ensureEqualsRecord( + RecordInvokeDynamic recordInvokeDynamic, ProgramAdditions programAdditions) { + DexMethod getFieldsAsObjects = ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions); + DexProgramClass clazz = recordInvokeDynamic.getRecordClass(); + DexMethod method = equalsRecordMethod(clazz.type); + assert clazz.lookupProgramMethod(method) == null; + programAdditions.accept( + method, () -> synthesizeEqualsRecordMethod(clazz, getFieldsAsObjects, method)); + return method; + } + + private DexMethod ensureGetFieldsAsObjects( + RecordInvokeDynamic recordInvokeDynamic, ProgramAdditions programAdditions) { + DexProgramClass clazz = recordInvokeDynamic.getRecordClass(); DexMethod method = getFieldsAsObjectsMethod(clazz.type); - synchronized (clazz.getMethodCollection()) { - ProgramMethod getFieldsAsObjects = clazz.lookupProgramMethod(method); - if (getFieldsAsObjects == null) { - getFieldsAsObjects = synthesizeGetFieldsAsObjectsMethod(clazz, fields, method); - clazz.addVirtualMethod(getFieldsAsObjects.getDefinition()); - if (eventConsumer != null) { - eventConsumer.acceptRecordMethod(getFieldsAsObjects); - } - } - } + assert clazz.lookupProgramMethod(method) == null; + programAdditions.accept( + method, + () -> synthesizeGetFieldsAsObjectsMethod(clazz, recordInvokeDynamic.getFields(), method)); + return method; } private DexMethod getFieldsAsObjectsMethod(DexType holder) { @@ -236,6 +328,13 @@ holder, factory.createProto(factory.objectArrayType), GET_FIELDS_AS_OBJECTS_METHOD_NAME); } + private DexMethod equalsRecordMethod(DexType holder) { + return factory.createMethod( + holder, + factory.createProto(factory.booleanType, factory.objectType), + EQUALS_RECORD_METHOD_NAME); + } + private ProgramMethod synthesizeRecordHelper( DexProto helperProto, BiFunction<InternalOptions, DexMethod, CfCode> codeGenerator, @@ -254,50 +353,54 @@ } private List<CfInstruction> desugarInvokeRecordHashCode( - DexProgramClass recordClass, - DexField[] fields, - CfInstructionDesugaringEventConsumer eventConsumer, + RecordInvokeDynamic recordInvokeDynamic, + LocalStackAllocator localStackAllocator, + RecordInstructionDesugaringEventConsumer eventConsumer, MethodProcessingContext methodProcessingContext) { - ensureGetFieldsAsObjects(recordClass, fields, eventConsumer); + localStackAllocator.allocateLocalStack(1); + DexMethod getFieldsAsObjects = + getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordClass().type); + assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null; + ArrayList<CfInstruction> instructions = new ArrayList<>(); + instructions.add(new CfStackInstruction(Dup)); + instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false)); + instructions.add(new CfStackInstruction(Swap)); + instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false)); ProgramMethod programMethod = synthesizeRecordHelper( recordHashCodeHelperProto, RecordCfMethods::RecordMethods_hashCode, methodProcessingContext); eventConsumer.acceptRecordMethod(programMethod); - return ImmutableList.of( - new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false)); + instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false)); + return instructions; } - private List<CfInstruction> desugarInvokeRecordEquals( - DexProgramClass recordClass, - DexField[] fields, - CfInstructionDesugaringEventConsumer eventConsumer, - MethodProcessingContext methodProcessingContext) { - ensureGetFieldsAsObjects(recordClass, fields, eventConsumer); - ProgramMethod programMethod = - synthesizeRecordHelper( - recordEqualsHelperProto, - RecordCfMethods::RecordMethods_equals, - methodProcessingContext); - eventConsumer.acceptRecordMethod(programMethod); - return ImmutableList.of( - new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false)); + private List<CfInstruction> desugarInvokeRecordEquals(RecordInvokeDynamic recordInvokeDynamic) { + DexMethod equalsRecord = equalsRecordMethod(recordInvokeDynamic.getRecordClass().type); + assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(equalsRecord) != null; + return Collections.singletonList(new CfInvoke(Opcodes.INVOKESPECIAL, equalsRecord, false)); } private List<CfInstruction> desugarInvokeRecordToString( - DexProgramClass recordClass, - DexString fieldNames, - DexField[] fields, - DexString simpleName, + RecordInvokeDynamic recordInvokeDynamic, LocalStackAllocator localStackAllocator, - CfInstructionDesugaringEventConsumer eventConsumer, + ProgramMethod context, + RecordInstructionDesugaringEventConsumer eventConsumer, MethodProcessingContext methodProcessingContext) { - ensureGetFieldsAsObjects(recordClass, fields, eventConsumer); - ArrayList<CfInstruction> instructions = new ArrayList<>(); - instructions.add(new CfConstString(simpleName)); - instructions.add(new CfConstString(fieldNames)); + DexString simpleName = + ClassNameComputationInfo.ClassNameMapping.SIMPLE_NAME.map( + recordInvokeDynamic.getRecordClass().type.toDescriptorString(), + context.getHolder(), + factory); localStackAllocator.allocateLocalStack(2); + DexMethod getFieldsAsObjects = + getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordClass().type); + assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null; + ArrayList<CfInstruction> instructions = new ArrayList<>(); + instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false)); + instructions.add(new CfConstString(simpleName)); + instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames())); ProgramMethod programMethod = synthesizeRecordHelper( recordToStringHelperProto, @@ -321,24 +424,35 @@ return false; } - private void requiresRecordClass(RecordDesugaringEventConsumer eventConsumer) { - DexProgramClass recordClass = synthesizeR8Record(); - if (recordClass != null) { - eventConsumer.acceptRecordClass(recordClass); - } + private void ensureRecordClass(RecordDesugaringEventConsumer eventConsumer) { + DexItemFactory factory = appView.dexItemFactory(); + checkRecordTagNotPresent(factory); + appView + .getSyntheticItems() + .ensureFixedClassFromType( + SyntheticNaming.SyntheticKind.RECORD_TAG, + factory.recordType, + appView, + builder -> { + DexEncodedMethod init = synthesizeRecordInitMethod(); + builder + .setAbstract() + .setDirectMethods(ImmutableList.of(init)); + }, + eventConsumer::acceptRecordClass); } - @Override - public boolean needsDesugaring(DexProgramClass clazz) { - return clazz.isRecord(); - } - - @Override - public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) { - if (clazz.isRecord()) { - assert clazz.superType == factory.recordType; - requiresRecordClass(eventConsumer); - clazz.accessFlags.unsetRecord(); + private void checkRecordTagNotPresent(DexItemFactory factory) { + DexClass r8RecordClass = + appView.appInfo().definitionForWithoutExistenceAssert(factory.recordTagType); + if (r8RecordClass != null && r8RecordClass.isProgramClass()) { + appView + .options() + .reporter + .error( + "D8/R8 is compiling a mix of desugared and non desugared input using" + + " java.lang.Record, but the application reader did not import correctly " + + factory.recordTagType); } } @@ -471,65 +585,6 @@ return factory.objectMembers.toString; } - private DexProgramClass synthesizeR8Record() { - DexItemFactory factory = appView.dexItemFactory(); - DexClass r8RecordClass = - appView.appInfo().definitionForWithoutExistenceAssert(factory.recordTagType); - if (r8RecordClass != null && r8RecordClass.isProgramClass()) { - appView - .options() - .reporter - .error( - "D8/R8 is compiling a mix of desugared and non desugared input using" - + " java.lang.Record, but the application reader did not import correctly " - + factory.recordTagType.toString()); - } - DexClass recordClass = - appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType); - if (recordClass != null && recordClass.isProgramClass()) { - return null; - } - return synchronizedSynthesizeR8Record(); - } - - private synchronized DexProgramClass synchronizedSynthesizeR8Record() { - DexItemFactory factory = appView.dexItemFactory(); - DexClass recordClass = - appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType); - if (recordClass != null && recordClass.isProgramClass()) { - return null; - } - DexEncodedMethod init = synthesizeRecordInitMethod(); - DexEncodedMethod abstractGetFieldsAsObjectsMethod = - synthesizeAbstractGetFieldsAsObjectsMethod(); - return appView - .getSyntheticItems() - .createFixedClassFromType( - SyntheticNaming.SyntheticKind.RECORD_TAG, - factory.recordType, - factory, - builder -> - builder - .setAbstract() - .setVirtualMethods(ImmutableList.of(abstractGetFieldsAsObjectsMethod)) - .setDirectMethods(ImmutableList.of(init))); - } - - private DexEncodedMethod synthesizeAbstractGetFieldsAsObjectsMethod() { - MethodAccessFlags methodAccessFlags = - MethodAccessFlags.fromSharedAccessFlags( - Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT, false); - DexMethod fieldsAsObjectsMethod = getFieldsAsObjectsMethod(factory.recordType); - return new DexEncodedMethod( - fieldsAsObjectsMethod, - methodAccessFlags, - MethodTypeSignature.noSignature(), - DexAnnotationSet.empty(), - ParameterAnnotationsList.empty(), - null, - true); - } - private DexEncodedMethod synthesizeRecordInitMethod() { MethodAccessFlags methodAccessFlags = MethodAccessFlags.fromSharedAccessFlags( @@ -547,4 +602,25 @@ new CallObjectInitCfCodeProvider(appView, factory.recordTagType).generateCfCode(), appView); return init; } + + @Override + public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) { + if (appView.appInfo().app().getFlags().hasReadProgramRecord()) { + ensureRecordClass(eventConsumer); + } + } + + @Override + public void postProcessingDesugaring( + Collection<DexProgramClass> programClasses, + CfPostProcessingDesugaringEventConsumer eventConsumer, + ExecutorService executorService) + throws ExecutionException { + for (DexProgramClass clazz : programClasses) { + if (clazz.isRecord()) { + assert clazz.superType == factory.recordType; + clazz.accessFlags.unsetRecord(); + } + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java index ed27028..ae702f9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -217,7 +217,7 @@ // Constant propagation. if (allowConstantPropagation) { - AbstractValue abstractValue = concreteParameterState.getAbstractValue(); + AbstractValue abstractValue = concreteParameterState.getAbstractValue(appView); if (abstractValue.isNonTrivial()) { newCallSiteInfo.constants.put(argumentIndex, abstractValue); isTop = false; @@ -234,20 +234,24 @@ if (allowConstantPropagation) { newCallSiteInfo.constants.put( argumentIndex, appView.abstractValueFactory().createNullValue()); + } else { + newCallSiteInfo.dynamicUpperBoundTypes.put( + argumentIndex, staticTypeElement.asArrayType().asDefinitelyNull()); } + isTop = false; } else if (nullability.isDefinitelyNotNull()) { newCallSiteInfo.dynamicUpperBoundTypes.put( argumentIndex, staticTypeElement.asArrayType().asDefinitelyNotNull()); + isTop = false; } else { - // Should never happen, since the parameter state is unknown in this case. + // The nullability should never be unknown, since we should use the unknown method state + // in this case. It should also not be bottom, since we should change the method's body + // to throw null in this case. assert false; } } else if (staticType.isClassType()) { - DynamicType dynamicType = - method.getDefinition().isInstance() && argumentIndex == 0 - ? concreteParameterState.asReceiverParameter().getDynamicType() - : concreteParameterState.asClassParameter().getDynamicType(); - if (!dynamicType.isTrivial(staticTypeElement)) { + DynamicType dynamicType = concreteParameterState.asReferenceParameter().getDynamicType(); + if (!dynamicType.isUnknown()) { newCallSiteInfo.dynamicUpperBoundTypes.put( argumentIndex, dynamicType.getDynamicUpperBoundType()); isTop = false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java index c3f2bc3..906ac7f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -147,6 +147,9 @@ } method.getHolder().removeMethod(method.getReference()); + + appView.withArgumentPropagator( + argumentPropagator -> argumentPropagator.transferArgumentInformation(method, parentMethod)); } private ProgramMethod resolveOnSuperClass(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java index e98adec..537d9af 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -38,6 +38,7 @@ import com.android.tools.r8.ir.regalloc.RegisterPositions.Type; import com.android.tools.r8.logging.Log; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.LinkedHashSetUtils; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.HashMultiset; @@ -60,6 +61,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -2524,9 +2526,10 @@ Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets, List<LiveIntervals> liveIntervals) { for (BasicBlock block : code.topologicallySortedBlocks()) { - Set<Value> live = Sets.newIdentityHashSet(); - Set<Value> phiOperands = Sets.newIdentityHashSet(); - Set<Value> liveAtThrowingInstruction = Sets.newIdentityHashSet(); + // Linked collections to ensure determinism of liveIntervals (see also b/197643889). + LinkedHashSet<Value> live = Sets.newLinkedHashSet(); + LinkedHashSet<Value> phiOperands = Sets.newLinkedHashSet(); + LinkedHashSet<Value> liveAtThrowingInstruction = Sets.newLinkedHashSet(); Set<BasicBlock> exceptionalSuccessors = block.getCatchHandlers().getUniqueTargets(); for (BasicBlock successor : block.getSuccessors()) { // Values live at entry to a block that is an exceptional successor are only live @@ -2534,9 +2537,10 @@ // the block only if they are used in normal flow as well. boolean isExceptionalSuccessor = exceptionalSuccessors.contains(successor); if (isExceptionalSuccessor) { - liveAtThrowingInstruction.addAll(liveAtEntrySets.get(successor).liveValues); + LinkedHashSetUtils.addAll( + liveAtThrowingInstruction, liveAtEntrySets.get(successor).liveValues); } else { - live.addAll(liveAtEntrySets.get(successor).liveValues); + LinkedHashSetUtils.addAll(live, liveAtEntrySets.get(successor).liveValues); } // Exception blocks should not have any phis (if an exception block has more than one // predecessor, then we insert a split block in-between). @@ -2546,7 +2550,7 @@ phiOperands.add(phi.getOperand(successor.getPredecessors().indexOf(block))); } } - live.addAll(phiOperands); + LinkedHashSetUtils.addAll(live, phiOperands); List<Instruction> instructions = block.getInstructions(); for (Value value : live) { int end = block.entry().getNumber() + instructions.size() * INSTRUCTION_NUMBER_DELTA; @@ -2635,7 +2639,9 @@ // In debug mode, or if the method is reachability sensitive, extend the live range // to cover the full scope of a local variable (encoded as debug values). int number = instruction.getNumber(); - for (Value use : instruction.getDebugValues()) { + List<Value> sortedDebugValues = new ArrayList<>(instruction.getDebugValues()); + sortedDebugValues.sort(Value::compareTo); + for (Value use : sortedDebugValues) { assert use.needsRegister(); if (!live.contains(use)) { live.add(use);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java new file mode 100644 index 0000000..ce66391 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -0,0 +1,170 @@ +// 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.synthetic; + +import com.android.tools.r8.cf.code.CfArrayStore; +import com.android.tools.r8.cf.code.CfCheckCast; +import com.android.tools.r8.cf.code.CfConstNumber; +import com.android.tools.r8.cf.code.CfFieldInstruction; +import com.android.tools.r8.cf.code.CfFrame; +import com.android.tools.r8.cf.code.CfFrame.FrameType; +import com.android.tools.r8.cf.code.CfIfCmp; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfLabel; +import com.android.tools.r8.cf.code.CfLoad; +import com.android.tools.r8.cf.code.CfNewArray; +import com.android.tools.r8.cf.code.CfReturn; +import com.android.tools.r8.cf.code.CfStore; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexField; +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.code.If; +import com.android.tools.r8.ir.code.MemberType; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.utils.collections.ImmutableDeque; +import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap; +import java.util.ArrayList; +import java.util.List; +import org.objectweb.asm.Opcodes; + +public abstract class RecordCfCodeProvider { + + /** + * Generates a method which answers all field values as an array of objects. If the field value is + * a primitive type, it uses the primitive wrapper to wrap it. + * + * <p>The fields in parameters are in the order where they should be in the array generated by the + * method, which is not necessarily the class instanceFields order. + * + * <p>Example: <code>record Person{ int age; String name;}</code> + * + * <p><code>Object[] getFieldsAsObjects() { + * Object[] fields = new Object[2]; + * fields[0] = name; + * fields[1] = Integer.valueOf(age); + * return fields;</code> + */ + public static class RecordGetFieldsAsObjectsCfCodeProvider extends SyntheticCfCodeProvider { + + public static void registerSynthesizedCodeReferences(DexItemFactory factory) { + factory.createSynthesizedType("[Ljava/lang/Object;"); + factory.primitiveToBoxed.forEach( + (primitiveType, boxedType) -> { + factory.createSynthesizedType(primitiveType.toDescriptorString()); + factory.createSynthesizedType(boxedType.toDescriptorString()); + }); + } + + private final DexField[] fields; + + public RecordGetFieldsAsObjectsCfCodeProvider( + AppView<?> appView, DexType holder, DexField[] fields) { + super(appView, holder); + this.fields = fields; + } + + @Override + public CfCode generateCfCode() { + // Stack layout: + // 0 : receiver (the record instance) + // 1 : the array to return + // 2+: spills + DexItemFactory factory = appView.dexItemFactory(); + List<CfInstruction> instructions = new ArrayList<>(); + // Object[] fields = new Object[*length*]; + instructions.add(new CfConstNumber(fields.length, ValueType.INT)); + instructions.add(new CfNewArray(factory.objectArrayType)); + instructions.add(new CfStore(ValueType.OBJECT, 1)); + // fields[*i*] = this.*field* || *PrimitiveWrapper*.valueOf(this.*field*); + for (int i = 0; i < fields.length; i++) { + DexField field = fields[i]; + instructions.add(new CfLoad(ValueType.OBJECT, 1)); + instructions.add(new CfConstNumber(i, ValueType.INT)); + instructions.add(new CfLoad(ValueType.OBJECT, 0)); + instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field)); + if (field.type.isPrimitiveType()) { + factory.primitiveToBoxed.forEach( + (primitiveType, boxedType) -> { + if (primitiveType == field.type) { + instructions.add( + new CfInvoke( + Opcodes.INVOKESTATIC, + factory.createMethod( + boxedType, + factory.createProto(boxedType, primitiveType), + factory.valueOfMethodName), + false)); + } + }); + } + instructions.add(new CfArrayStore(MemberType.OBJECT)); + } + // return fields; + instructions.add(new CfLoad(ValueType.OBJECT, 1)); + instructions.add(new CfReturn(ValueType.OBJECT)); + return standardCfCodeFromInstructions(instructions); + } + } + + public static class RecordEqualsCfCodeProvider extends SyntheticCfCodeProvider { + + private final DexMethod getFieldsAsObjects; + + public RecordEqualsCfCodeProvider( + AppView<?> appView, DexType holder, DexMethod getFieldsAsObjects) { + super(appView, holder); + this.getFieldsAsObjects = getFieldsAsObjects; + } + + public static void registerSynthesizedCodeReferences(DexItemFactory factory) { + factory.createSynthesizedType("[Ljava/lang/Object;"); + factory.createSynthesizedType("[Ljava/util/Arrays;"); + } + + @Override + public CfCode generateCfCode() { + // This generates something along the lines of: + // if (this.getClass() != other.getClass()) { + // return false; + // } + // return Arrays.equals( + // recordInstance.getFieldsAsObjects(), + // ((RecordClass) other).getFieldsAsObjects()); + ImmutableInt2ReferenceSortedMap<FrameType> locals = + ImmutableInt2ReferenceSortedMap.<FrameType>builder() + .put(0, FrameType.initialized(getHolder())) + .put(1, FrameType.initialized(appView.dexItemFactory().objectType)) + .build(); + DexItemFactory factory = appView.dexItemFactory(); + List<CfInstruction> instructions = new ArrayList<>(); + CfLabel fieldCmp = new CfLabel(); + ValueType recordType = ValueType.fromDexType(getHolder()); + ValueType objectType = ValueType.fromDexType(factory.objectType); + instructions.add(new CfLoad(recordType, 0)); + instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false)); + instructions.add(new CfLoad(objectType, 1)); + instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false)); + instructions.add(new CfIfCmp(If.Type.EQ, ValueType.OBJECT, fieldCmp)); + instructions.add(new CfConstNumber(0, ValueType.INT)); + instructions.add(new CfReturn(ValueType.INT)); + instructions.add(fieldCmp); + instructions.add(new CfFrame(locals, ImmutableDeque.of())); + instructions.add(new CfLoad(recordType, 0)); + instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false)); + instructions.add(new CfLoad(objectType, 1)); + instructions.add(new CfCheckCast(getHolder())); + instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false)); + instructions.add( + new CfInvoke( + Opcodes.INVOKESTATIC, factory.javaUtilArraysMethods.equalsObjectArray, false)); + instructions.add(new CfReturn(ValueType.INT)); + return standardCfCodeFromInstructions(instructions); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java deleted file mode 100644 index 4f534da..0000000 --- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java +++ /dev/null
@@ -1,102 +0,0 @@ -// Copyright (c) 2019, 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.synthetic; - -import com.android.tools.r8.cf.code.CfArrayStore; -import com.android.tools.r8.cf.code.CfConstNumber; -import com.android.tools.r8.cf.code.CfFieldInstruction; -import com.android.tools.r8.cf.code.CfInstruction; -import com.android.tools.r8.cf.code.CfInvoke; -import com.android.tools.r8.cf.code.CfLoad; -import com.android.tools.r8.cf.code.CfNewArray; -import com.android.tools.r8.cf.code.CfReturn; -import com.android.tools.r8.cf.code.CfStore; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.CfCode; -import com.android.tools.r8.graph.DexField; -import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.ir.code.MemberType; -import com.android.tools.r8.ir.code.ValueType; -import java.util.ArrayList; -import java.util.List; -import org.objectweb.asm.Opcodes; - -/** - * Generates a method which answers all field values as an array of objects. If the field value is a - * primitive type, it uses the primitive wrapper to wrap it. - * - * <p>The fields in parameters are in the order where they should be in the array generated by the - * method, which is not necessarily the class instanceFields order. - * - * <p>Example: <code>record Person{ int age; String name;}</code> - * - * <p><code>Object[] getFieldsAsObjects() { - * Object[] fields = new Object[2]; - * fields[0] = name; - * fields[1] = Integer.valueOf(age); - * return fields;</code> - */ -public class RecordGetFieldsAsObjectsCfCodeProvider extends SyntheticCfCodeProvider { - - public static void registerSynthesizedCodeReferences(DexItemFactory factory) { - factory.createSynthesizedType("[Ljava/lang/Object;"); - factory.primitiveToBoxed.forEach( - (primitiveType, boxedType) -> { - factory.createSynthesizedType(primitiveType.toDescriptorString()); - factory.createSynthesizedType(boxedType.toDescriptorString()); - }); - } - - private final DexField[] fields; - - public RecordGetFieldsAsObjectsCfCodeProvider( - AppView<?> appView, DexType holder, DexField[] fields) { - super(appView, holder); - this.fields = fields; - } - - @Override - public CfCode generateCfCode() { - // Stack layout: - // 0 : receiver (the record instance) - // 1 : the array to return - // 2+: spills - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - // Object[] fields = new Object[*length*]; - instructions.add(new CfConstNumber(fields.length, ValueType.INT)); - instructions.add(new CfNewArray(factory.objectArrayType)); - instructions.add(new CfStore(ValueType.OBJECT, 1)); - // fields[*i*] = this.*field* || *PrimitiveWrapper*.valueOf(this.*field*); - for (int i = 0; i < fields.length; i++) { - DexField field = fields[i]; - instructions.add(new CfLoad(ValueType.OBJECT, 1)); - instructions.add(new CfConstNumber(i, ValueType.INT)); - instructions.add(new CfLoad(ValueType.OBJECT, 0)); - instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field)); - if (field.type.isPrimitiveType()) { - factory.primitiveToBoxed.forEach( - (primitiveType, boxedType) -> { - if (primitiveType == field.type) { - instructions.add( - new CfInvoke( - Opcodes.INVOKESTATIC, - factory.createMethod( - boxedType, - factory.createProto(boxedType, primitiveType), - factory.valueOfMethodName), - false)); - } - }); - } - instructions.add(new CfArrayStore(MemberType.OBJECT)); - } - // return fields; - instructions.add(new CfLoad(ValueType.OBJECT, 1)); - instructions.add(new CfReturn(ValueType.OBJECT)); - return standardCfCodeFromInstructions(instructions); - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java index c01b0b8..05f88a1 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java
@@ -22,6 +22,10 @@ this.holder = holder; } + public DexType getHolder() { + return holder; + } + public abstract CfCode generateCfCode(); protected CfCode standardCfCodeFromInstructions(List<CfInstruction> instructions) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java index d166380..9e79ee0 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -18,7 +18,7 @@ import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import com.google.common.collect.ImmutableList; -import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -174,7 +174,7 @@ rewritten |= typeAlias.rewrite(typeAliasProvider, appView, namingLens); } // For properties, we need to combine potentially a field, setter and getter. - Map<KotlinPropertyInfo, KotlinPropertyGroup> properties = new IdentityHashMap<>(); + Map<KotlinPropertyInfo, KotlinPropertyGroup> properties = new LinkedHashMap<>(); for (DexEncodedField field : clazz.fields()) { if (field.getKotlinInfo().isProperty()) { properties
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java index 00ea1ac..2173883 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.optimize.argumentpropagation; +import static com.android.tools.r8.optimize.argumentpropagation.utils.StronglyConnectedProgramClasses.computeStronglyConnectedProgramClasses; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -14,13 +16,18 @@ import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.PostMethodProcessor; import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; /** Optimization that propagates information about arguments from call sites to method entries. */ -// TODO(b/190154391): Add timing information for performance tracking. public class ArgumentPropagator { private final AppView<AppInfoWithLiveness> appView; @@ -48,67 +55,118 @@ * Called by {@link IRConverter} *before* the primary optimization pass to setup the scanner for * collecting argument information from the code objects. */ - public void initializeCodeScanner() { + public void initializeCodeScanner(ExecutorService executorService, Timing timing) + throws ExecutionException { + assert !appView.getSyntheticItems().hasPendingSyntheticClasses(); + + timing.begin("Argument propagator"); + timing.begin("Initialize code scanner"); + codeScanner = new ArgumentPropagatorCodeScanner(appView); - // Disable argument propagation for methods that should not be optimized. ImmediateProgramSubtypingInfo immediateSubtypingInfo = ImmediateProgramSubtypingInfo.create(appView); - // TODO(b/190154391): Consider computing the strongly connected components and running this in - // parallel for each scc. - new ArgumentPropagatorUnoptimizableMethods( - appView, immediateSubtypingInfo, codeScanner.getMethodStates()) - .disableArgumentPropagationForUnoptimizableMethods(appView.appInfo().classes()); + List<Set<DexProgramClass>> stronglyConnectedProgramClasses = + computeStronglyConnectedProgramClasses(appView, immediateSubtypingInfo); + ThreadUtils.processItems( + stronglyConnectedProgramClasses, + classes -> { + // Disable argument propagation for methods that should not be optimized by setting their + // method state to unknown. + new ArgumentPropagatorUnoptimizableMethods( + appView, immediateSubtypingInfo, codeScanner.getMethodStates()) + .disableArgumentPropagationForUnoptimizableMethods(classes); + + // Compute the mapping from virtual methods to their root virtual method and the set of + // monomorphic virtual methods. + new VirtualRootMethodsAnalysis(appView, immediateSubtypingInfo) + .extendVirtualRootMethods(appView.appInfo().classes(), codeScanner); + }, + executorService); + + timing.end(); + timing.end(); } /** Called by {@link IRConverter} prior to finalizing methods. */ - public void scan(ProgramMethod method, IRCode code, MethodProcessor methodProcessor) { + public void scan( + ProgramMethod method, IRCode code, MethodProcessor methodProcessor, Timing timing) { if (codeScanner != null) { // TODO(b/190154391): Do we process synthetic methods using a OneTimeMethodProcessor // during the primary optimization pass? assert methodProcessor.isPrimaryMethodProcessor(); - codeScanner.scan(method, code); + codeScanner.scan(method, code, timing); } else { assert !methodProcessor.isPrimaryMethodProcessor(); } } + public void transferArgumentInformation(ProgramMethod from, ProgramMethod to) { + assert codeScanner != null; + MethodStateCollectionByReference methodStates = codeScanner.getMethodStates(); + MethodState methodState = methodStates.remove(from); + if (!methodState.isBottom()) { + methodStates.addMethodState(appView, to, methodState); + } + } + + public void tearDownCodeScanner( + PostMethodProcessor.Builder postMethodProcessorBuilder, + ExecutorService executorService, + Timing timing) + throws ExecutionException { + assert !appView.getSyntheticItems().hasPendingSyntheticClasses(); + timing.begin("Argument propagator"); + populateParameterOptimizationInfo(executorService, timing); + optimizeMethodParameters(); + enqueueMethodsForProcessing(postMethodProcessorBuilder); + timing.end(); + } + /** * Called by {@link IRConverter} *after* the primary optimization pass to populate the parameter * optimization info. */ - public void populateParameterOptimizationInfo(ExecutorService executorService) + private void populateParameterOptimizationInfo(ExecutorService executorService, Timing timing) throws ExecutionException { // Unset the scanner since all code objects have been scanned at this point. assert appView.isAllCodeProcessed(); MethodStateCollectionByReference codeScannerResult = codeScanner.getMethodStates(); + appView.testing().argumentPropagatorEventConsumer.acceptCodeScannerResult(codeScannerResult); codeScanner = null; + timing.begin("Compute optimization info"); new ArgumentPropagatorOptimizationInfoPopulator(appView, codeScannerResult) - .populateOptimizationInfo(executorService); + .populateOptimizationInfo(executorService, timing); + timing.end(); } /** Called by {@link IRConverter} to optimize method definitions. */ - public void optimizeMethodParameters() { + private void optimizeMethodParameters() { // TODO(b/190154391): Remove parameters with constant values. // TODO(b/190154391): Remove unused parameters by simulating they are constant. // TODO(b/190154391): Strengthen the static type of parameters. // TODO(b/190154391): If we learn that a method returns a constant, then consider changing its // return type to void. + // TODO(b/69963623): If we optimize a method to be unconditionally throwing (because it has a + // bottom parameter), then for each caller that becomes unconditionally throwing, we could + // also enqueue the caller's callers for reprocessing. This would propagate the throwing + // information to all call sites. } /** * Called by {@link IRConverter} to add all methods that require reprocessing to {@param * postMethodProcessorBuilder}. */ - public void enqueueMethodsForProcessing(PostMethodProcessor.Builder postMethodProcessorBuilder) { + private void enqueueMethodsForProcessing(PostMethodProcessor.Builder postMethodProcessorBuilder) { for (DexProgramClass clazz : appView.appInfo().classes()) { clazz.forEachProgramMethodMatching( DexEncodedMethod::hasCode, method -> { CallSiteOptimizationInfo callSiteOptimizationInfo = method.getDefinition().getCallSiteOptimizationInfo(); - if (callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) { + if (callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo() + && !appView.appInfo().isNeverReprocessMethod(method.getReference())) { postMethodProcessorBuilder.add(method); } });
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java index 509079e..77beb56 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -4,13 +4,14 @@ package com.android.tools.r8.optimize.argumentpropagation; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexMethodHandle; -import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.DynamicType; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeElement; @@ -27,8 +28,10 @@ import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeParameterState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeParameterState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodStateOrBottom; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodStateOrUnknown; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodState; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodStateOrBottom; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeParameterState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReceiverParameterState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter; @@ -36,12 +39,16 @@ import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState; +import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * Analyzes each {@link IRCode} during the primary optimization to collect information about the @@ -49,64 +56,68 @@ * * <p>State pruning is applied on-the-fly to avoid storing redundant information. */ -class ArgumentPropagatorCodeScanner { +public class ArgumentPropagatorCodeScanner { private static AliasedValueConfiguration aliasedValueConfiguration = AssumeAndCheckCastAliasedValueConfiguration.getInstance(); private final AppView<AppInfoWithLiveness> appView; + private final Set<DexMethod> monomorphicVirtualMethods = Sets.newIdentityHashSet(); + /** - * Maps each non-interface method to the upper most method in the super class chain with the same - * method signature. This only contains an entry for non-private virtual methods that override - * another method in the program. + * Maps each non-private virtual method to the upper most method in the class hierarchy with the + * same method signature. Virtual methods that do not override other virtual methods are mapped to + * themselves. */ - private final Map<DexMethod, DexMethod> classMethodRoots; + private final Map<DexMethod, DexMethod> virtualRootMethods = new IdentityHashMap<>(); /** * The abstract program state for this optimization. Intuitively maps each parameter to its * abstract value and dynamic type. */ - private final MethodStateCollectionByReference methodStates; + private final MethodStateCollectionByReference methodStates = + MethodStateCollectionByReference.createConcurrent(); ArgumentPropagatorCodeScanner(AppView<AppInfoWithLiveness> appView) { this.appView = appView; - this.classMethodRoots = computeClassMethodRoots(); - this.methodStates = computeInitialMethodStates(); } - private Map<DexMethod, DexMethod> computeClassMethodRoots() { - // TODO(b/190154391): Group methods related by overriding to enable more effective pruning. - Map<DexMethod, DexMethod> roots = new IdentityHashMap<>(); - for (DexProgramClass clazz : appView.appInfo().classes()) { - clazz.forEachProgramVirtualMethod( - method -> roots.put(method.getReference(), method.getReference())); - } - return roots; + public synchronized void addMonomorphicVirtualMethods(Set<DexMethod> extension) { + monomorphicVirtualMethods.addAll(extension); } - private MethodStateCollectionByReference computeInitialMethodStates() { - // TODO(b/190154391): There is no need to track an abstract value for receivers; we only care - // about the dynamic type for such parameters. Consider initializing the initial state to have - // unknown abstract values for all receivers. - return MethodStateCollectionByReference.createConcurrent(); + public synchronized void addVirtualRootMethods(Map<DexMethod, DexMethod> extension) { + virtualRootMethods.putAll(extension); } MethodStateCollectionByReference getMethodStates() { return methodStates; } - void scan(ProgramMethod method, IRCode code) { + DexMethod getVirtualRootMethod(ProgramMethod method) { + return virtualRootMethods.get(method.getReference()); + } + + boolean isMonomorphicVirtualMethod(ProgramMethod method) { + boolean isMonomorphicVirtualMethod = monomorphicVirtualMethods.contains(method.getReference()); + assert method.getDefinition().belongsToVirtualPool() || !isMonomorphicVirtualMethod; + return isMonomorphicVirtualMethod; + } + + void scan(ProgramMethod method, IRCode code, Timing timing) { + timing.begin("Argument propagation scanner"); for (Invoke invoke : code.<Invoke>instructions(Instruction::isInvoke)) { if (invoke.isInvokeMethod()) { - scan(invoke.asInvokeMethod(), method); + scan(invoke.asInvokeMethod(), method, timing); } else if (invoke.isInvokeCustom()) { scan(invoke.asInvokeCustom(), method); } } + timing.end(); } - private void scan(InvokeMethod invoke, ProgramMethod context) { + private void scan(InvokeMethod invoke, ProgramMethod context, Timing timing) { List<Value> arguments = invoke.arguments(); if (arguments.isEmpty()) { // Nothing to propagate. @@ -115,7 +126,13 @@ DexMethod invokedMethod = invoke.getInvokedMethod(); if (invokedMethod.getHolderType().isArrayType()) { - // Nothing to propagate. + // Nothing to propagate; the targeted method is not a program method. + return; + } + + if (invoke.isInvokeMethodWithReceiver() + && invoke.asInvokeMethodWithReceiver().getReceiver().isAlwaysNull(appView)) { + // Nothing to propagate; the invoke instruction always fails. return; } @@ -126,7 +143,6 @@ return; } - // TODO(b/190154391): Also bail out if the method is an unoptimizable program method. if (!resolutionResult.getResolvedHolder().isProgramClass()) { // Nothing to propagate; this could dispatch to a program method, but we cannot optimize // methods that override non-program methods. @@ -147,6 +163,28 @@ return; } + if (invoke.isInvokeSuper()) { + // Use the super target instead of the resolved method to ensure that we propagate the + // argument information to the targeted method. + DexClassAndMethod target = + resolutionResult.lookupInvokeSuperTarget(context.getHolder(), appView.appInfo()); + if (target == null) { + // Nothing to propagate; the invoke instruction fails. + return; + } + if (!target.isProgramMethod()) { + throw new Unreachable( + "Expected super target of a non-library override to be a program method (" + + "resolved program method: " + + resolvedMethod + + ", " + + "super non-program method: " + + target + + ")"); + } + resolvedMethod = target.asProgramMethod(); + } + // Find the method where to store the information about the arguments from this invoke. // If the invoke may dispatch to more than one method, we intentionally do not compute all // possible dispatch targets and propagate the information to these methods (this is expensive). @@ -155,53 +193,147 @@ DexMethod representativeMethodReference = getRepresentativeForPolymorphicInvokeOrElse( invoke, resolvedMethod, resolvedMethod.getReference()); - methodStates.addMethodState( + ProgramMethod finalResolvedMethod = resolvedMethod; + timing.begin("Add method state"); + methodStates.addTemporaryMethodState( appView, representativeMethodReference, - () -> computeMethodState(invoke, resolvedMethod, context)); + existingMethodState -> + computeMethodState(invoke, finalResolvedMethod, context, existingMethodState, timing), + timing); + timing.end(); } private MethodState computeMethodState( - InvokeMethod invoke, ProgramMethod resolvedMethod, ProgramMethod context) { + InvokeMethod invoke, + ProgramMethod resolvedMethod, + ProgramMethod context, + MethodState existingMethodState, + Timing timing) { + assert !existingMethodState.isUnknown(); + // If this invoke may target at most one method, then we compute a state that maps each // parameter to the abstract value and dynamic type provided by this call site. Otherwise, we // compute a polymorphic method state, which includes information about the receiver's dynamic // type bounds. + timing.begin("Compute method state for invoke"); boolean isPolymorphicInvoke = getRepresentativeForPolymorphicInvokeOrElse(invoke, resolvedMethod, null) != null; - return isPolymorphicInvoke - ? computePolymorphicMethodState(invoke.asInvokeMethodWithReceiver(), context) - : computeMonomorphicMethodState(invoke, context); + MethodState result; + if (isPolymorphicInvoke) { + assert existingMethodState.isBottom() || existingMethodState.isPolymorphic(); + result = + computePolymorphicMethodState( + invoke.asInvokeMethodWithReceiver(), + resolvedMethod, + context, + existingMethodState.asPolymorphicOrBottom()); + } else { + assert existingMethodState.isBottom() || existingMethodState.isMonomorphic(); + result = + computeMonomorphicMethodState( + invoke, resolvedMethod, context, existingMethodState.asMonomorphicOrBottom()); + } + timing.end(); + return result; } // TODO(b/190154391): Add a strategy that widens the dynamic receiver type to allow easily // experimenting with the performance/size trade-off between precise/imprecise handling of // dynamic dispatch. private MethodState computePolymorphicMethodState( - InvokeMethodWithReceiver invoke, ProgramMethod context) { + InvokeMethodWithReceiver invoke, + ProgramMethod resolvedMethod, + ProgramMethod context, + ConcretePolymorphicMethodStateOrBottom existingMethodState) { DynamicType dynamicReceiverType = invoke.getReceiver().getDynamicType(appView); - ConcretePolymorphicMethodState methodState = - new ConcretePolymorphicMethodState( - dynamicReceiverType, - computeMonomorphicMethodState(invoke, context, dynamicReceiverType)); - // TODO(b/190154391): If the receiver type is effectively unknown, and the computed monomorphic - // method state is also unknown (i.e., we have "unknown receiver type" -> "unknown method - // state"), then return the canonicalized UnknownMethodState instance instead. - return methodState; + assert !dynamicReceiverType.getDynamicUpperBoundType().nullability().isDefinitelyNull(); + + DynamicType bounds = + computeBoundsForPolymorphicMethodState( + invoke, resolvedMethod, context, dynamicReceiverType); + MethodState existingMethodStateForBounds = + existingMethodState.isPolymorphic() + ? existingMethodState.asPolymorphic().getMethodStateForBounds(bounds) + : MethodState.bottom(); + + if (existingMethodStateForBounds.isPolymorphic()) { + assert false; + return MethodState.unknown(); + } + + // If we already don't know anything about the parameters for the given type bounds, then don't + // compute a method state. + if (existingMethodStateForBounds.isUnknown()) { + return MethodState.unknown(); + } + + ConcreteMonomorphicMethodStateOrUnknown methodStateForBounds = + computeMonomorphicMethodState( + invoke, + resolvedMethod, + context, + existingMethodStateForBounds.asMonomorphicOrBottom(), + dynamicReceiverType); + return ConcretePolymorphicMethodState.create(bounds, methodStateForBounds); + } + + private DynamicType computeBoundsForPolymorphicMethodState( + InvokeMethodWithReceiver invoke, + ProgramMethod resolvedMethod, + ProgramMethod context, + DynamicType dynamicReceiverType) { + ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context); + DynamicType bounds = + singleTarget != null + ? DynamicType.createExact( + singleTarget.getHolderType().toTypeElement(appView).asClassType()) + : dynamicReceiverType.withNullability(Nullability.maybeNull()); + + // We intentionally drop the nullability for the type bounds. This increases the number of + // collisions in the polymorphic method states, which does not change the precision (since the + // nullability does not have any impact on the possible dispatch targets) and is good for state + // pruning. + assert bounds.getDynamicUpperBoundType().nullability().isMaybeNull(); + + // If the bounds are trivial (i.e., the upper bound is equal to the holder of the virtual root + // method), then widen the type bounds to 'unknown'. + DexMethod virtualRootMethod = getVirtualRootMethod(resolvedMethod); + if (virtualRootMethod == null) { + assert false : "Unexpected virtual method without root: " + resolvedMethod; + return bounds; + } + + DynamicType trivialBounds = + DynamicType.create( + appView, virtualRootMethod.getHolderType().toTypeElement(appView).asClassType()); + if (bounds.equals(trivialBounds)) { + return DynamicType.unknown(); + } + return bounds; } private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState( - InvokeMethod invoke, ProgramMethod context) { + InvokeMethod invoke, + ProgramMethod resolvedMethod, + ProgramMethod context, + ConcreteMonomorphicMethodStateOrBottom existingMethodState) { return computeMonomorphicMethodState( invoke, + resolvedMethod, context, + existingMethodState, invoke.isInvokeMethodWithReceiver() ? invoke.getFirstArgument().getDynamicType(appView) : null); } private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState( - InvokeMethod invoke, ProgramMethod context, DynamicType dynamicReceiverType) { + InvokeMethod invoke, + ProgramMethod resolvedMethod, + ProgramMethod context, + ConcreteMonomorphicMethodStateOrBottom existingMethodState, + DynamicType dynamicReceiverType) { List<ParameterState> parameterStates = new ArrayList<>(invoke.arguments().size()); int argumentIndex = 0; @@ -209,14 +341,21 @@ assert dynamicReceiverType != null; parameterStates.add( computeParameterStateForReceiver( - invoke.asInvokeMethodWithReceiver(), dynamicReceiverType)); + invoke.asInvokeMethodWithReceiver(), + resolvedMethod, + dynamicReceiverType, + existingMethodState)); argumentIndex++; } for (; argumentIndex < invoke.arguments().size(); argumentIndex++) { parameterStates.add( computeParameterStateForNonReceiver( - invoke, argumentIndex, invoke.getArgument(argumentIndex), context)); + invoke, + argumentIndex, + invoke.getArgument(argumentIndex), + context, + existingMethodState)); } // If all parameter states are unknown, then return a canonicalized unknown method state that @@ -233,27 +372,39 @@ // TODO(b/190154391): Consider validating the above hypothesis by using // computeParameterStateForNonReceiver() for receivers. private ParameterState computeParameterStateForReceiver( - InvokeMethodWithReceiver invoke, DynamicType dynamicReceiverType) { - ClassTypeElement staticReceiverType = - invoke - .getInvokedMethod() - .getHolderType() - .toTypeElement(appView) - .asClassType() - .asMeetWithNotNull(); - return dynamicReceiverType.isTrivial(staticReceiverType) + InvokeMethodWithReceiver invoke, + ProgramMethod resolvedMethod, + DynamicType dynamicReceiverType, + ConcreteMonomorphicMethodStateOrBottom existingMethodState) { + // Don't compute a state for this parameter if the stored state is already unknown. + if (existingMethodState.isMonomorphic() + && existingMethodState.asMonomorphic().getParameterState(0).isUnknown()) { + return ParameterState.unknown(); + } + + DynamicType widenedDynamicReceiverType = + WideningUtils.widenDynamicReceiverType(appView, resolvedMethod, dynamicReceiverType); + return widenedDynamicReceiverType.isUnknown() ? ParameterState.unknown() : new ConcreteReceiverParameterState(dynamicReceiverType); } private ParameterState computeParameterStateForNonReceiver( - InvokeMethod invoke, int argumentIndex, Value argument, ProgramMethod context) { + InvokeMethod invoke, + int argumentIndex, + Value argument, + ProgramMethod context, + ConcreteMonomorphicMethodStateOrBottom existingMethodState) { + // Don't compute a state for this parameter if the stored state is already unknown. + if (existingMethodState.isMonomorphic() + && existingMethodState.asMonomorphic().getParameterState(argumentIndex).isUnknown()) { + return ParameterState.unknown(); + } + Value argumentRoot = argument.getAliasedValue(aliasedValueConfiguration); - TypeElement parameterType = - invoke - .getInvokedMethod() - .getArgumentType(argumentIndex, invoke.isInvokeStatic()) - .toTypeElement(appView); + DexType parameterType = + invoke.getInvokedMethod().getArgumentType(argumentIndex, invoke.isInvokeStatic()); + TypeElement parameterTypeElement = parameterType.toTypeElement(appView); // If the value is an argument of the enclosing method, then clearly we have no information // about its abstract value. Instead of treating this as having an unknown runtime value, we @@ -264,18 +415,18 @@ MethodParameter forwardedParameter = new MethodParameter( context.getReference(), argumentRoot.getDefinition().asArgument().getIndex()); - if (parameterType.isClassType()) { + if (parameterTypeElement.isClassType()) { return new ConcreteClassTypeParameterState(forwardedParameter); - } else if (parameterType.isArrayType()) { + } else if (parameterTypeElement.isArrayType()) { return new ConcreteArrayTypeParameterState(forwardedParameter); } else { - assert parameterType.isPrimitiveType(); + assert parameterTypeElement.isPrimitiveType(); return new ConcretePrimitiveTypeParameterState(forwardedParameter); } } // Only track the nullability for array types. - if (parameterType.isArrayType()) { + if (parameterTypeElement.isArrayType()) { Nullability nullability = argument.getType().nullability(); return nullability.isMaybeNull() ? ParameterState.unknown() @@ -286,16 +437,18 @@ // For class types, we track both the abstract value and the dynamic type. If both are unknown, // then use UnknownParameterState. - if (parameterType.isClassType()) { + if (parameterTypeElement.isClassType()) { DynamicType dynamicType = argument.getDynamicType(appView); - return abstractValue.isUnknown() && dynamicType.isTrivial(parameterType) + DynamicType widenedDynamicType = + WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType); + return abstractValue.isUnknown() && widenedDynamicType.isUnknown() ? ParameterState.unknown() - : new ConcreteClassTypeParameterState(abstractValue, dynamicType); + : new ConcreteClassTypeParameterState(abstractValue, widenedDynamicType); } // For primitive types, we only track the abstract value, thus if the abstract value is unknown, // we use UnknownParameterState. - assert parameterType.isPrimitiveType(); + assert parameterTypeElement.isPrimitiveType(); return abstractValue.isUnknown() ? ParameterState.unknown() : new ConcretePrimitiveTypeParameterState(abstractValue); @@ -303,16 +456,24 @@ private DexMethod getRepresentativeForPolymorphicInvokeOrElse( InvokeMethod invoke, ProgramMethod resolvedMethod, DexMethod defaultValue) { - DexMethod resolvedMethodReference = resolvedMethod.getReference(); + if (resolvedMethod.getDefinition().belongsToDirectPool()) { + return defaultValue; + } + if (invoke.isInvokeInterface()) { - return resolvedMethodReference; + assert !isMonomorphicVirtualMethod(resolvedMethod); + return getVirtualRootMethod(resolvedMethod); } - DexMethod rootMethod = classMethodRoots.get(resolvedMethodReference); - if (rootMethod != null) { - assert invoke.isInvokeVirtual(); - return rootMethod; + + assert invoke.isInvokeSuper() || invoke.isInvokeVirtual(); + + if (isMonomorphicVirtualMethod(resolvedMethod)) { + return defaultValue; } - return defaultValue; + + DexMethod rootMethod = getVirtualRootMethod(resolvedMethod); + assert rootMethod != null; + return rootMethod; } private void scan(InvokeCustom invoke, ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java new file mode 100644 index 0000000..c02d1ad --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java
@@ -0,0 +1,33 @@ +// 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.optimize.argumentpropagation; + +import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; + +public interface ArgumentPropagatorEventConsumer { + + static ArgumentPropagatorEventConsumer emptyConsumer() { + return new ArgumentPropagatorEventConsumer() { + @Override + public void acceptCodeScannerResult(MethodStateCollectionByReference methodStates) { + // Intentionally empty. + } + }; + } + + void acceptCodeScannerResult(MethodStateCollectionByReference methodStates); + + default ArgumentPropagatorEventConsumer andThen( + ArgumentPropagatorEventConsumer nextEventConsumer) { + ArgumentPropagatorEventConsumer self = this; + return new ArgumentPropagatorEventConsumer() { + @Override + public void acceptCodeScannerResult(MethodStateCollectionByReference methodStates) { + self.acceptCodeScannerResult(methodStates); + nextEventConsumer.acceptCodeScannerResult(methodStates); + } + }; + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorIROptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorIROptimizer.java index 6f7ec2c..e7060d3 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorIROptimizer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorIROptimizer.java
@@ -78,7 +78,11 @@ if (argumentValue.getType().isReferenceType()) { TypeElement dynamicUpperBoundType = optimizationInfo.getDynamicUpperBoundType(argument.getIndex()); - if (dynamicUpperBoundType == null) { + if (dynamicUpperBoundType == null || dynamicUpperBoundType.isTop()) { + continue; + } + if (dynamicUpperBoundType.isBottom()) { + assert false; continue; } if (dynamicUpperBoundType.isDefinitelyNull()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java index dbec312..6d4f3b7 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -4,10 +4,15 @@ package com.android.tools.r8.optimize.argumentpropagation; +import static com.android.tools.r8.optimize.argumentpropagation.utils.StronglyConnectedProgramClasses.computeStronglyConnectedProgramClasses; + import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.DynamicType; import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState; @@ -18,15 +23,15 @@ import com.android.tools.r8.optimize.argumentpropagation.propagation.InParameterFlowPropagator; import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator; import com.android.tools.r8.optimize.argumentpropagation.propagation.VirtualDispatchMethodArgumentPropagator; +import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.ThreadUtils; -import com.android.tools.r8.utils.WorkList; -import com.google.common.collect.Sets; -import java.util.ArrayList; +import com.android.tools.r8.utils.Timing; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.stream.IntStream; /** * Propagates the argument flow information collected by the {@link ArgumentPropagatorCodeScanner}. @@ -50,47 +55,15 @@ ImmediateProgramSubtypingInfo.create(appView); this.immediateSubtypingInfo = immediateSubtypingInfo; this.stronglyConnectedComponents = - computeStronglyConnectedComponents(appView, immediateSubtypingInfo); - } - - /** - * Computes the strongly connected components in the program class hierarchy (where extends and - * implements edges are treated as bidirectional). - * - * <p>All strongly connected components can be processed in parallel. - */ - private static List<Set<DexProgramClass>> computeStronglyConnectedComponents( - AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { - Set<DexProgramClass> seen = Sets.newIdentityHashSet(); - List<Set<DexProgramClass>> stronglyConnectedComponents = new ArrayList<>(); - for (DexProgramClass clazz : appView.appInfo().classes()) { - if (seen.contains(clazz)) { - continue; - } - Set<DexProgramClass> stronglyConnectedComponent = - computeStronglyConnectedComponent(clazz, immediateSubtypingInfo); - stronglyConnectedComponents.add(stronglyConnectedComponent); - seen.addAll(stronglyConnectedComponent); - } - return stronglyConnectedComponents; - } - - private static Set<DexProgramClass> computeStronglyConnectedComponent( - DexProgramClass clazz, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { - WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(clazz); - while (worklist.hasNext()) { - DexProgramClass current = worklist.next(); - immediateSubtypingInfo.forEachImmediateProgramSuperClass(current, worklist::addIfNotSeen); - worklist.addIfNotSeen(immediateSubtypingInfo.getSubclasses(current)); - } - return worklist.getSeenSet(); + computeStronglyConnectedProgramClasses(appView, immediateSubtypingInfo); } /** * Computes an over-approximation of each parameter's value and type and stores the result in * {@link com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}. */ - void populateOptimizationInfo(ExecutorService executorService) throws ExecutionException { + void populateOptimizationInfo(ExecutorService executorService, Timing timing) + throws ExecutionException { // TODO(b/190154391): Propagate argument information to handle virtual dispatch. // TODO(b/190154391): To deal with arguments that are themselves passed as arguments to invoke // instructions, build a flow graph where nodes are parameters and there is an edge from a @@ -99,14 +72,20 @@ // TODO(b/190154391): If we learn that parameter p1 is constant, and that the enclosing method // returns p1 according to the optimization info, then update the optimization info to describe // that the method returns the constant. + timing.begin("Propagate argument information for virtual methods"); ThreadUtils.processItems( stronglyConnectedComponents, this::processStronglyConnectedComponent, executorService); + timing.end(); // Solve the parameter flow constraints. + timing.begin("Solve flow constraints"); new InParameterFlowPropagator(appView, methodStates).run(executorService); + timing.end(); // The information stored on each method is now sound, and can be used as optimization info. + timing.begin("Set optimization info"); setOptimizationInfo(executorService); + timing.end(); assert methodStates.isEmpty(); } @@ -130,6 +109,13 @@ // of the method's holder. All that remains is to propagate the information downwards in the // class hierarchy to propagate the argument information for a non-private virtual method to its // overrides. + // TODO(b/190154391): Before running the top-down traversal, consider lowering the argument + // information for non-private virtual methods. If we have some argument information with upper + // bound=B, which is stored on a method on class A, we could move this argument information + // from class A to B. This way we could potentially get rid of the "inactive argument + // information" during the depth-first class hierarchy traversal, since the argument + // information would be active by construction when it is first seen during the top-down class + // hierarchy traversal. new VirtualDispatchMethodArgumentPropagator(appView, immediateSubtypingInfo, methodStates) .run(stronglyConnectedComponent); } @@ -144,10 +130,20 @@ } private void setOptimizationInfo(ProgramMethod method) { - MethodState methodState = methodStates.remove(method); + MethodState methodState = methodStates.removeOrElse(method, null); + if (methodState == null) { + return; + } + if (methodState.isBottom()) { - // TODO(b/190154391): This should only happen if the method is never called. Consider removing - // the method in this case. + if (!appView.options().canUseDefaultAndStaticInterfaceMethods() + && method.getHolder().isInterface()) { + // TODO(b/190154391): The method has not been moved to the companion class yet, so we can't + // remove its code object. + return; + } + DexEncodedMethod definition = method.getDefinition(); + definition.setCode(definition.buildEmptyThrowingCode(appView.options()), appView); return; } @@ -163,11 +159,37 @@ } ConcreteMonomorphicMethodState monomorphicMethodState = concreteMethodState.asMonomorphic(); + + // Verify that there is no parameter with bottom info. + assert monomorphicMethodState.getParameterStates().stream().noneMatch(ParameterState::isBottom); + + // Verify that all in-parameter information has been pruned by the InParameterFlowPropagator. assert monomorphicMethodState.getParameterStates().stream() .filter(ParameterState::isConcrete) .map(ParameterState::asConcrete) .noneMatch(ConcreteParameterState::hasInParameters); + // Verify that the dynamic type information is correct. + assert IntStream.range(0, monomorphicMethodState.getParameterStates().size()) + .filter( + index -> { + ParameterState parameterState = monomorphicMethodState.getParameterState(index); + return parameterState.isConcrete() && parameterState.asConcrete().isClassParameter(); + }) + .allMatch( + index -> { + DynamicType dynamicType = + monomorphicMethodState + .getParameterState(index) + .asConcrete() + .asClassParameter() + .getDynamicType(); + DexType staticType = method.getArgumentType(index); + assert dynamicType + == WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, staticType); + return true; + }); + method .getDefinition() .joinCallSiteOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java index 36dae85..0e643f6 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
@@ -95,7 +95,10 @@ for (DexProgramClass clazz : stronglyConnectedComponent) { clazz.forEachProgramMethod( method -> { - assert !method.getDefinition().isLibraryMethodOverride().isUnknown(); + assert !method.getDefinition().belongsToVirtualPool() + || !method.getDefinition().isLibraryMethodOverride().isUnknown() + : "Unexpected virtual method without library method override information: " + + method.toSourceString(); if (method.getDefinition().isLibraryMethodOverride().isPossiblyTrue() || appInfo.isMethodTargetedByInvokeDynamic(method) || !appInfo
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeParameterState.java new file mode 100644 index 0000000..666c87b --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeParameterState.java
@@ -0,0 +1,46 @@ +// 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.optimize.argumentpropagation.codescanner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Action; + +public class BottomArrayTypeParameterState extends BottomParameterState { + + private static final BottomArrayTypeParameterState INSTANCE = new BottomArrayTypeParameterState(); + + private BottomArrayTypeParameterState() {} + + public static BottomArrayTypeParameterState get() { + return INSTANCE; + } + + @Override + public ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + if (parameterState.isBottom()) { + return this; + } + if (parameterState.isUnknown()) { + return parameterState; + } + assert parameterState.isConcrete(); + assert parameterState.asConcrete().isReferenceParameter(); + ConcreteReferenceTypeParameterState concreteParameterState = + parameterState.asConcrete().asReferenceParameter(); + Nullability nullability = concreteParameterState.getNullability(); + if (nullability.isMaybeNull()) { + return unknown(); + } + return new ConcreteArrayTypeParameterState( + nullability, concreteParameterState.copyInParameters()); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeParameterState.java new file mode 100644 index 0000000..d1df0ac --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeParameterState.java
@@ -0,0 +1,50 @@ +// 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.optimize.argumentpropagation.codescanner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.DynamicType; +import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Action; + +public class BottomClassTypeParameterState extends BottomParameterState { + + private static final BottomClassTypeParameterState INSTANCE = new BottomClassTypeParameterState(); + + private BottomClassTypeParameterState() {} + + public static BottomClassTypeParameterState get() { + return INSTANCE; + } + + @Override + public ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + if (parameterState.isBottom()) { + return this; + } + if (parameterState.isUnknown()) { + return parameterState; + } + assert parameterState.isConcrete(); + assert parameterState.asConcrete().isReferenceParameter(); + ConcreteReferenceTypeParameterState concreteParameterState = + parameterState.asConcrete().asReferenceParameter(); + AbstractValue abstractValue = concreteParameterState.getAbstractValue(appView); + DynamicType dynamicType = concreteParameterState.getDynamicType(); + DynamicType widenedDynamicType = + WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType); + return abstractValue.isUnknown() && widenedDynamicType.isUnknown() + ? unknown() + : new ConcreteClassTypeParameterState( + abstractValue, widenedDynamicType, concreteParameterState.copyInParameters()); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java index fea97a2..f5f117e 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
@@ -5,10 +5,12 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import java.util.function.Supplier; +import java.util.function.Function; -public class BottomMethodState extends MethodStateBase { +public class BottomMethodState extends MethodStateBase + implements ConcreteMonomorphicMethodStateOrBottom, ConcretePolymorphicMethodStateOrBottom { private static final BottomMethodState INSTANCE = new BottomMethodState(); @@ -24,13 +26,33 @@ } @Override - public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) { - return methodState; + public ConcreteMonomorphicMethodStateOrBottom asMonomorphicOrBottom() { + return this; + } + + @Override + public ConcretePolymorphicMethodStateOrBottom asPolymorphicOrBottom() { + return this; + } + + @Override + public MethodState mutableCopy() { + return this; } @Override public MethodState mutableJoin( - AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) { - return methodStateSupplier.get(); + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + MethodState methodState) { + return methodState.mutableCopy(); + } + + @Override + public MethodState mutableJoin( + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + Function<MethodState, MethodState> methodStateSupplier) { + return mutableJoin(appView, methodSignature, methodStateSupplier.apply(this)); } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomParameterState.java new file mode 100644 index 0000000..a1a3908 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomParameterState.java
@@ -0,0 +1,29 @@ +// 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.optimize.argumentpropagation.codescanner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.shaking.AppInfoWithLiveness; + +public abstract class BottomParameterState extends ParameterState { + + BottomParameterState() {} + + @Override + public final AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) { + return AbstractValue.bottom(); + } + + @Override + public final boolean isBottom() { + return true; + } + + @Override + public final ParameterState mutableCopy() { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeParameterState.java new file mode 100644 index 0000000..fc88145 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeParameterState.java
@@ -0,0 +1,40 @@ +// 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.optimize.argumentpropagation.codescanner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Action; + +public class BottomPrimitiveTypeParameterState extends BottomParameterState { + + private static final BottomPrimitiveTypeParameterState INSTANCE = + new BottomPrimitiveTypeParameterState(); + + private BottomPrimitiveTypeParameterState() {} + + public static BottomPrimitiveTypeParameterState get() { + return INSTANCE; + } + + @Override + public ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + if (parameterState.isBottom()) { + assert parameterState == bottomPrimitiveTypeParameter(); + return this; + } + if (parameterState.isUnknown()) { + return parameterState; + } + assert parameterState.isConcrete(); + assert parameterState.asConcrete().isPrimitiveParameter(); + return parameterState.mutableCopy(); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverParameterState.java new file mode 100644 index 0000000..3a94858 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverParameterState.java
@@ -0,0 +1,46 @@ +// 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.optimize.argumentpropagation.codescanner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.DynamicType; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Action; + +public class BottomReceiverParameterState extends BottomParameterState { + + private static final BottomReceiverParameterState INSTANCE = new BottomReceiverParameterState(); + + private BottomReceiverParameterState() {} + + public static BottomReceiverParameterState get() { + return INSTANCE; + } + + @Override + public ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + if (parameterState.isBottom()) { + return this; + } + if (parameterState.isUnknown()) { + return parameterState; + } + assert parameterState.isConcrete(); + assert parameterState.asConcrete().isReferenceParameter(); + ConcreteReferenceTypeParameterState concreteParameterState = + parameterState.asConcrete().asReferenceParameter(); + DynamicType dynamicType = concreteParameterState.getDynamicType(); + if (dynamicType.isUnknown()) { + return unknown(); + } + return new ConcreteReceiverParameterState( + dynamicType, concreteParameterState.copyInParameters()); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java index e7533e5..eb75ac9 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
@@ -4,48 +4,68 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.DynamicType; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; +import com.android.tools.r8.utils.SetUtils; +import java.util.Collections; +import java.util.Set; -public class ConcreteArrayTypeParameterState extends ConcreteParameterState { +public class ConcreteArrayTypeParameterState extends ConcreteReferenceTypeParameterState { private Nullability nullability; public ConcreteArrayTypeParameterState(MethodParameter inParameter) { - super(inParameter); - this.nullability = Nullability.bottom(); + this(Nullability.bottom(), SetUtils.newHashSet(inParameter)); } public ConcreteArrayTypeParameterState(Nullability nullability) { - assert !nullability.isMaybeNull() : "Must use UnknownParameterState instead"; - this.nullability = nullability; + this(nullability, Collections.emptySet()); } - public ParameterState mutableJoin( - ConcreteArrayTypeParameterState parameterState, Action onChangedAction) { - assert !nullability.isMaybeNull(); - assert !parameterState.nullability.isMaybeNull(); - boolean inParametersChanged = mutableJoinInParameters(parameterState); - if (widenInParameters()) { - return unknown(); + public ConcreteArrayTypeParameterState( + Nullability nullability, Set<MethodParameter> inParameters) { + super(inParameters); + this.nullability = nullability; + assert !isEffectivelyBottom() : "Must use BottomArrayTypeParameterState instead"; + assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead"; + } + + @Override + public ParameterState clearInParameters() { + if (hasInParameters()) { + if (nullability.isBottom()) { + return bottomArrayTypeParameter(); + } + internalClearInParameters(); } - if (inParametersChanged) { - onChangedAction.execute(); - } + assert !isEffectivelyBottom(); return this; } @Override - public AbstractValue getAbstractValue() { + public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) { + if (getNullability().isDefinitelyNull()) { + return appView.abstractValueFactory().createNullValue(); + } return AbstractValue.unknown(); } @Override + public DynamicType getDynamicType() { + return DynamicType.unknown(); + } + + @Override public ConcreteParameterStateKind getKind() { return ConcreteParameterStateKind.ARRAY; } + @Override public Nullability getNullability() { return nullability; } @@ -59,4 +79,39 @@ public ConcreteArrayTypeParameterState asArrayParameter() { return this; } + + public boolean isEffectivelyBottom() { + return nullability.isBottom() && !hasInParameters(); + } + + public boolean isEffectivelyUnknown() { + return nullability.isMaybeNull(); + } + + @Override + public ParameterState mutableCopy() { + return new ConcreteArrayTypeParameterState(nullability, copyInParameters()); + } + + @Override + public ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ConcreteReferenceTypeParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + assert parameterType.isArrayType(); + assert !nullability.isUnknown(); + nullability = nullability.join(parameterState.getNullability()); + if (nullability.isUnknown()) { + return unknown(); + } + boolean inParametersChanged = mutableJoinInParameters(parameterState); + if (widenInParameters()) { + return unknown(); + } + if (inParametersChanged) { + onChangedAction.execute(); + } + return this; + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java index 3c59a07..6dc6c5c 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -5,69 +5,68 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.type.DynamicType; +import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; +import com.android.tools.r8.utils.SetUtils; +import java.util.Collections; +import java.util.Set; -public class ConcreteClassTypeParameterState extends ConcreteParameterState { +public class ConcreteClassTypeParameterState extends ConcreteReferenceTypeParameterState { private AbstractValue abstractValue; private DynamicType dynamicType; public ConcreteClassTypeParameterState(MethodParameter inParameter) { - super(inParameter); - this.abstractValue = AbstractValue.bottom(); - this.dynamicType = DynamicType.bottom(); + this(AbstractValue.bottom(), DynamicType.bottom(), SetUtils.newHashSet(inParameter)); } public ConcreteClassTypeParameterState(AbstractValue abstractValue, DynamicType dynamicType) { - this.abstractValue = abstractValue; - this.dynamicType = dynamicType; + this(abstractValue, dynamicType, Collections.emptySet()); } - public ParameterState mutableJoin( - AppView<AppInfoWithLiveness> appView, - ConcreteClassTypeParameterState parameterState, - Action onChangedAction) { - boolean allowNullOrAbstractValue = true; - boolean allowNonConstantNumbers = false; - AbstractValue oldAbstractValue = abstractValue; - abstractValue = - abstractValue.join( - parameterState.abstractValue, - appView.abstractValueFactory(), - allowNullOrAbstractValue, - allowNonConstantNumbers); - // TODO(b/190154391): Join the dynamic types using SubtypingInfo. - // TODO(b/190154391): Take in the static type as an argument, and unset the dynamic type if it - // equals the static type. - DynamicType oldDynamicType = dynamicType; - dynamicType = - dynamicType.equals(parameterState.dynamicType) ? dynamicType : DynamicType.unknown(); - if (abstractValue.isUnknown() && dynamicType.isUnknown()) { - return unknown(); + public ConcreteClassTypeParameterState( + AbstractValue abstractValue, DynamicType dynamicType, Set<MethodParameter> inParameters) { + super(inParameters); + this.abstractValue = abstractValue; + this.dynamicType = dynamicType; + assert !isEffectivelyBottom() : "Must use BottomClassTypeParameterState instead"; + assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead"; + } + + @Override + public ParameterState clearInParameters() { + if (hasInParameters()) { + if (abstractValue.isBottom()) { + assert dynamicType.isBottom(); + return bottomClassTypeParameter(); + } + internalClearInParameters(); } - boolean inParametersChanged = mutableJoinInParameters(parameterState); - if (widenInParameters()) { - return unknown(); - } - if (abstractValue != oldAbstractValue || dynamicType != oldDynamicType || inParametersChanged) { - onChangedAction.execute(); - } + assert !isEffectivelyBottom(); return this; } @Override - public AbstractValue getAbstractValue() { + public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) { return abstractValue; } + @Override public DynamicType getDynamicType() { return dynamicType; } @Override + public Nullability getNullability() { + return getDynamicType().getDynamicUpperBoundType().nullability(); + } + + @Override public ConcreteParameterStateKind getKind() { return ConcreteParameterStateKind.CLASS; } @@ -81,4 +80,53 @@ public ConcreteClassTypeParameterState asClassParameter() { return this; } + + public boolean isEffectivelyBottom() { + return abstractValue.isBottom() && dynamicType.isBottom() && !hasInParameters(); + } + + public boolean isEffectivelyUnknown() { + return abstractValue.isUnknown() && dynamicType.isUnknown(); + } + + @Override + public ParameterState mutableCopy() { + return new ConcreteClassTypeParameterState(abstractValue, dynamicType, copyInParameters()); + } + + @Override + public ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ConcreteReferenceTypeParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + assert parameterType.isClassType(); + boolean allowNullOrAbstractValue = true; + boolean allowNonConstantNumbers = false; + AbstractValue oldAbstractValue = abstractValue; + abstractValue = + abstractValue.join( + parameterState.getAbstractValue(appView), + appView.abstractValueFactory(), + allowNullOrAbstractValue, + allowNonConstantNumbers); + DynamicType oldDynamicType = dynamicType; + DynamicType joinedDynamicType = dynamicType.join(appView, parameterState.getDynamicType()); + DynamicType widenedDynamicType = + WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, parameterType); + dynamicType = widenedDynamicType; + if (abstractValue.isUnknown() && dynamicType.isUnknown()) { + return unknown(); + } + boolean inParametersChanged = mutableJoinInParameters(parameterState); + if (widenInParameters()) { + return unknown(); + } + if (abstractValue != oldAbstractValue + || !dynamicType.equals(oldDynamicType) + || inParametersChanged) { + onChangedAction.execute(); + } + return this; + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java index 6aae75f..ecd3e08 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
@@ -5,8 +5,9 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import java.util.function.Supplier; +import java.util.function.Function; public abstract class ConcreteMethodState extends MethodStateBase { @@ -20,38 +21,37 @@ return this; } - public boolean isPolymorphic() { - return false; - } - - public ConcretePolymorphicMethodState asPolymorphic() { - return null; - } - @Override - public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) { + public MethodState mutableJoin( + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + MethodState methodState) { if (methodState.isBottom()) { return this; } if (methodState.isUnknown()) { return methodState; } - return mutableJoin(appView, methodState.asConcrete()); + return mutableJoin(appView, methodSignature, methodState.asConcrete()); } @Override public MethodState mutableJoin( - AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) { - return mutableJoin(appView, methodStateSupplier.get()); + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + Function<MethodState, MethodState> methodStateSupplier) { + return mutableJoin(appView, methodSignature, methodStateSupplier.apply(this)); } private MethodState mutableJoin( - AppView<AppInfoWithLiveness> appView, ConcreteMethodState methodState) { + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + ConcreteMethodState methodState) { if (isMonomorphic() && methodState.isMonomorphic()) { - return asMonomorphic().mutableJoin(appView, methodState.asMonomorphic()); + return asMonomorphic().mutableJoin(appView, methodSignature, methodState.asMonomorphic()); } if (isPolymorphic() && methodState.isPolymorphic()) { - return asPolymorphic().mutableJoin(appView, methodState.asPolymorphic()); + return asPolymorphic().mutableJoin(appView, methodSignature, methodState.asPolymorphic()); } assert false; return unknown();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java index fc8fe25..b49e48f 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
@@ -5,16 +5,22 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.google.common.collect.Iterables; +import com.google.common.collect.Streams; +import java.util.ArrayList; import java.util.List; public class ConcreteMonomorphicMethodState extends ConcreteMethodState - implements ConcreteMonomorphicMethodStateOrUnknown { + implements ConcreteMonomorphicMethodStateOrBottom, ConcreteMonomorphicMethodStateOrUnknown { List<ParameterState> parameterStates; public ConcreteMonomorphicMethodState(List<ParameterState> parameterStates) { + assert Streams.stream(Iterables.skip(parameterStates, 1)) + .noneMatch(x -> x.isConcrete() && x.asConcrete().isReceiverParameter()); assert Iterables.any(parameterStates, parameterState -> !parameterState.isUnknown()) : "Must use UnknownMethodState instead"; this.parameterStates = parameterStates; @@ -28,17 +34,43 @@ return parameterStates; } + @Override + public ConcreteMonomorphicMethodState mutableCopy() { + List<ParameterState> copiedParametersStates = new ArrayList<>(size()); + for (ParameterState parameterState : getParameterStates()) { + copiedParametersStates.add(parameterState.mutableCopy()); + } + return new ConcreteMonomorphicMethodState(copiedParametersStates); + } + public ConcreteMonomorphicMethodStateOrUnknown mutableJoin( - AppView<AppInfoWithLiveness> appView, ConcreteMonomorphicMethodState methodState) { + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + ConcreteMonomorphicMethodState methodState) { if (size() != methodState.size()) { assert false; return unknown(); } - for (int i = 0; i < size(); i++) { - ParameterState parameterState = parameterStates.get(i); - ParameterState otherParameterState = methodState.parameterStates.get(i); - parameterStates.set(i, parameterState.mutableJoin(appView, otherParameterState)); + int argumentIndex = 0; + if (size() > methodSignature.getArity()) { + assert size() == methodSignature.getArity() + 1; + ParameterState parameterState = parameterStates.get(0); + ParameterState otherParameterState = methodState.parameterStates.get(0); + DexType parameterType = null; + parameterStates.set( + 0, parameterState.mutableJoin(appView, otherParameterState, parameterType)); + argumentIndex++; + } + + for (int parameterIndex = 0; argumentIndex < size(); argumentIndex++, parameterIndex++) { + ParameterState parameterState = parameterStates.get(argumentIndex); + ParameterState otherParameterState = methodState.parameterStates.get(argumentIndex); + DexType parameterType = methodSignature.getParameter(parameterIndex); + parameterStates.set( + argumentIndex, parameterState.mutableJoin(appView, otherParameterState, parameterType)); + assert !parameterStates.get(argumentIndex).isConcrete() + || !parameterStates.get(argumentIndex).asConcrete().isReceiverParameter(); } if (Iterables.all(parameterStates, ParameterState::isUnknown)) { @@ -57,7 +89,15 @@ return this; } + @Override + public ConcreteMonomorphicMethodStateOrBottom asMonomorphicOrBottom() { + return this; + } + public void setParameterState(int index, ParameterState parameterState) { + assert index == 0 + || !parameterState.isConcrete() + || !parameterState.asConcrete().isReceiverParameter(); parameterStates.set(index, parameterState); }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrBottom.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrBottom.java new file mode 100644 index 0000000..9528fd7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrBottom.java
@@ -0,0 +1,7 @@ +// 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.optimize.argumentpropagation.codescanner; + +public interface ConcreteMonomorphicMethodStateOrBottom extends MethodState {}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrUnknown.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrUnknown.java index 1b90e84..2239ffd 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrUnknown.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrUnknown.java
@@ -4,4 +4,8 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; -public interface ConcreteMonomorphicMethodStateOrUnknown extends MethodState {} +public interface ConcreteMonomorphicMethodStateOrUnknown extends MethodState { + + @Override + ConcreteMonomorphicMethodStateOrUnknown mutableCopy(); +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java index 1e5ebae..3b9da36 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java
@@ -5,16 +5,16 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; -import com.android.tools.r8.utils.SetUtils; -import com.google.common.collect.Sets; import java.util.Collections; +import java.util.HashSet; import java.util.Set; -public abstract class ConcreteParameterState extends ParameterState { +public abstract class ConcreteParameterState extends NonEmptyParameterState { - enum ConcreteParameterStateKind { + public enum ConcreteParameterStateKind { ARRAY, CLASS, PRIMITIVE, @@ -23,16 +23,22 @@ private Set<MethodParameter> inParameters; - ConcreteParameterState() { - this.inParameters = Collections.emptySet(); + ConcreteParameterState(Set<MethodParameter> inParameters) { + this.inParameters = inParameters; } - ConcreteParameterState(MethodParameter inParameter) { - this.inParameters = SetUtils.newHashSet(inParameter); + public abstract ParameterState clearInParameters(); + + void internalClearInParameters() { + inParameters = Collections.emptySet(); } - public void clearInParameters() { - inParameters.clear(); + public Set<MethodParameter> copyInParameters() { + if (inParameters.isEmpty()) { + assert inParameters == Collections.<MethodParameter>emptySet(); + return inParameters; + } + return new HashSet<>(inParameters); } public boolean hasInParameters() { @@ -40,6 +46,7 @@ } public Set<MethodParameter> getInParameters() { + assert inParameters.isEmpty() || inParameters instanceof HashSet<?>; return inParameters; } @@ -77,6 +84,14 @@ return null; } + public boolean isReferenceParameter() { + return false; + } + + public ConcreteReferenceTypeParameterState asReferenceParameter() { + return null; + } + @Override public boolean isConcrete() { return true; @@ -88,36 +103,30 @@ } @Override - public ParameterState mutableJoin( - AppView<AppInfoWithLiveness> appView, ParameterState parameterState, Action onChangedAction) { + public final ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + if (parameterState.isBottom()) { + return this; + } if (parameterState.isUnknown()) { return parameterState; } - ConcreteParameterStateKind kind = getKind(); - ConcreteParameterStateKind otherKind = parameterState.asConcrete().getKind(); - if (kind == otherKind) { - switch (getKind()) { - case ARRAY: - return asArrayParameter() - .mutableJoin(parameterState.asConcrete().asArrayParameter(), onChangedAction); - case CLASS: - return asClassParameter() - .mutableJoin( - appView, parameterState.asConcrete().asClassParameter(), onChangedAction); - case PRIMITIVE: - return asPrimitiveParameter() - .mutableJoin( - appView, parameterState.asConcrete().asPrimitiveParameter(), onChangedAction); - case RECEIVER: - return asReceiverParameter() - .mutableJoin(parameterState.asConcrete().asReceiverParameter(), onChangedAction); - default: - // Dead. - } + ConcreteParameterState concreteParameterState = parameterState.asConcrete(); + if (isReferenceParameter()) { + assert concreteParameterState.isReferenceParameter(); + return asReferenceParameter() + .mutableJoin( + appView, + concreteParameterState.asReferenceParameter(), + parameterType, + onChangedAction); } - - assert false; - return unknown(); + return asPrimitiveParameter() + .mutableJoin( + appView, concreteParameterState.asPrimitiveParameter(), parameterType, onChangedAction); } boolean mutableJoinInParameters(ConcreteParameterState parameterState) { @@ -126,7 +135,7 @@ } if (inParameters.isEmpty()) { assert inParameters == Collections.<MethodParameter>emptySet(); - inParameters = Sets.newIdentityHashSet(); + inParameters = new HashSet<>(); } return inParameters.addAll(parameterState.inParameters); }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java index 2e5a908..c4a1feb 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
@@ -5,62 +5,169 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.ir.analysis.type.DynamicType; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.function.BiConsumer; +import java.util.function.Function; -public class ConcretePolymorphicMethodState extends ConcreteMethodState { +public class ConcretePolymorphicMethodState extends ConcreteMethodState + implements ConcretePolymorphicMethodStateOrBottom, ConcretePolymorphicMethodStateOrUnknown { - private final Map<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> receiverBoundsToState = - new HashMap<>(); + private final Map<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> receiverBoundsToState; - public ConcretePolymorphicMethodState( - DynamicType receiverBounds, ConcreteMonomorphicMethodStateOrUnknown methodState) { - // TODO(b/190154391): Ensure that we use the unknown state instead of mapping unknown -> unknown - // here. - receiverBoundsToState.put(receiverBounds, methodState); + private ConcretePolymorphicMethodState( + Map<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> receiverBoundsToState) { + this.receiverBoundsToState = receiverBoundsToState; + assert !isEffectivelyBottom(); + assert !isEffectivelyUnknown(); } - public void forEach(BiConsumer<DynamicType, MethodState> consumer) { + private ConcretePolymorphicMethodState( + DynamicType receiverBounds, ConcreteMonomorphicMethodStateOrUnknown methodState) { + this.receiverBoundsToState = new HashMap<>(1); + receiverBoundsToState.put(receiverBounds, methodState); + assert !isEffectivelyUnknown(); + } + + public static ConcretePolymorphicMethodStateOrUnknown create( + DynamicType receiverBounds, ConcreteMonomorphicMethodStateOrUnknown methodState) { + return receiverBounds.isUnknown() && methodState.isUnknown() + ? MethodState.unknown() + : new ConcretePolymorphicMethodState(receiverBounds, methodState); + } + + private ConcretePolymorphicMethodStateOrUnknown add( + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + DynamicType bounds, + ConcreteMonomorphicMethodStateOrUnknown methodState) { + assert !isEffectivelyBottom(); + assert !isEffectivelyUnknown(); + if (methodState.isUnknown()) { + if (bounds.isUnknown()) { + return unknown(); + } else { + receiverBoundsToState.put(bounds, methodState); + return this; + } + } else { + assert methodState.isMonomorphic(); + ConcreteMonomorphicMethodStateOrUnknown newMethodStateForBounds = + joinInner(appView, methodSignature, receiverBoundsToState.get(bounds), methodState); + if (bounds.isUnknown() && newMethodStateForBounds.isUnknown()) { + return unknown(); + } else { + receiverBoundsToState.put(bounds, newMethodStateForBounds); + return this; + } + } + } + + private static ConcreteMonomorphicMethodStateOrUnknown joinInner( + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + ConcreteMonomorphicMethodStateOrUnknown methodState, + ConcreteMonomorphicMethodStateOrUnknown other) { + if (methodState == null) { + return other.mutableCopy(); + } + if (methodState.isUnknown() || other.isUnknown()) { + return unknown(); + } + assert methodState.isMonomorphic(); + return methodState.asMonomorphic().mutableJoin(appView, methodSignature, other.asMonomorphic()); + } + + public void forEach( + BiConsumer<? super DynamicType, ? super ConcreteMonomorphicMethodStateOrUnknown> consumer) { receiverBoundsToState.forEach(consumer); } - public boolean isEmpty() { + public MethodState getMethodStateForBounds(DynamicType dynamicType) { + ConcreteMonomorphicMethodStateOrUnknown methodStateForBounds = + receiverBoundsToState.get(dynamicType); + if (methodStateForBounds != null) { + return methodStateForBounds; + } + return MethodState.bottom(); + } + + public boolean isEffectivelyBottom() { return receiverBoundsToState.isEmpty(); } + public boolean isEffectivelyUnknown() { + return getMethodStateForBounds(DynamicType.unknown()).isUnknown(); + } + + @Override + public MethodState mutableCopy() { + assert !isEffectivelyBottom(); + assert !isEffectivelyUnknown(); + Map<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> receiverBoundsToState = + new HashMap<>(); + forEach((bounds, methodState) -> receiverBoundsToState.put(bounds, methodState.mutableCopy())); + return new ConcretePolymorphicMethodState(receiverBoundsToState); + } + + public MethodState mutableCopyWithRewrittenBounds( + AppView<AppInfoWithLiveness> appView, + Function<DynamicType, DynamicType> boundsRewriter, + DexMethodSignature methodSignature) { + assert !isEffectivelyBottom(); + assert !isEffectivelyUnknown(); + Map<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> rewrittenReceiverBoundsToState = + new HashMap<>(); + for (Entry<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> entry : + receiverBoundsToState.entrySet()) { + DynamicType rewrittenBounds = boundsRewriter.apply(entry.getKey()); + if (rewrittenBounds == null) { + continue; + } + ConcreteMonomorphicMethodStateOrUnknown existingMethodStateForBounds = + rewrittenReceiverBoundsToState.get(rewrittenBounds); + ConcreteMonomorphicMethodStateOrUnknown newMethodStateForBounds = + joinInner(appView, methodSignature, existingMethodStateForBounds, entry.getValue()); + if (rewrittenBounds.isUnknown() && newMethodStateForBounds.isUnknown()) { + return unknown(); + } + rewrittenReceiverBoundsToState.put(rewrittenBounds, newMethodStateForBounds); + } + return rewrittenReceiverBoundsToState.isEmpty() + ? bottom() + : new ConcretePolymorphicMethodState(rewrittenReceiverBoundsToState); + } + public MethodState mutableJoin( - AppView<AppInfoWithLiveness> appView, ConcretePolymorphicMethodState methodState) { - assert !isEmpty(); - assert !methodState.isEmpty(); - methodState.receiverBoundsToState.forEach( - (receiverBounds, stateToAdd) -> { - if (stateToAdd.isUnknown()) { - receiverBoundsToState.put(receiverBounds, stateToAdd); - } else { - assert stateToAdd.isMonomorphic(); - receiverBoundsToState.compute( - receiverBounds, - (ignore, existingState) -> { - if (existingState == null) { - return stateToAdd; - } - if (existingState.isUnknown()) { - return existingState; - } - assert existingState.isMonomorphic(); - return existingState - .asMonomorphic() - .mutableJoin(appView, stateToAdd.asMonomorphic()); - }); - } - }); - // TODO(b/190154391): Widen to unknown when the unknown dynamic type is mapped to unknown. + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + ConcretePolymorphicMethodState methodState) { + assert !isEffectivelyBottom(); + assert !isEffectivelyUnknown(); + assert !methodState.isEffectivelyBottom(); + assert !methodState.isEffectivelyUnknown(); + for (Entry<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> entry : + methodState.receiverBoundsToState.entrySet()) { + ConcretePolymorphicMethodStateOrUnknown result = + add(appView, methodSignature, entry.getKey(), entry.getValue()); + if (result.isUnknown()) { + return result; + } + assert result == this; + } + assert !isEffectivelyUnknown(); return this; } + public Collection<ConcreteMonomorphicMethodStateOrUnknown> values() { + return receiverBoundsToState.values(); + } + @Override public boolean isPolymorphic() { return true; @@ -70,4 +177,9 @@ public ConcretePolymorphicMethodState asPolymorphic() { return this; } + + @Override + public ConcretePolymorphicMethodStateOrBottom asPolymorphicOrBottom() { + return this; + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodStateOrBottom.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodStateOrBottom.java new file mode 100644 index 0000000..6577c0e --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodStateOrBottom.java
@@ -0,0 +1,7 @@ +// 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.optimize.argumentpropagation.codescanner; + +public interface ConcretePolymorphicMethodStateOrBottom extends MethodState {}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodStateOrUnknown.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodStateOrUnknown.java new file mode 100644 index 0000000..ae7bdfd --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodStateOrUnknown.java
@@ -0,0 +1,7 @@ +// 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.optimize.argumentpropagation.codescanner; + +public interface ConcretePolymorphicMethodStateOrUnknown extends MethodState {}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java index 0b85fb6..3f1baff 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
@@ -5,28 +5,57 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; +import com.android.tools.r8.utils.SetUtils; +import java.util.Collections; +import java.util.Set; public class ConcretePrimitiveTypeParameterState extends ConcreteParameterState { private AbstractValue abstractValue; public ConcretePrimitiveTypeParameterState(AbstractValue abstractValue) { - assert !abstractValue.isUnknown() : "Must use UnknownParameterState"; + this(abstractValue, Collections.emptySet()); + } + + public ConcretePrimitiveTypeParameterState( + AbstractValue abstractValue, Set<MethodParameter> inParameters) { + super(inParameters); this.abstractValue = abstractValue; + assert !isEffectivelyBottom() : "Must use BottomPrimitiveTypeParameterState instead"; + assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead"; } public ConcretePrimitiveTypeParameterState(MethodParameter inParameter) { - super(inParameter); - this.abstractValue = AbstractValue.bottom(); + this(AbstractValue.bottom(), SetUtils.newHashSet(inParameter)); + } + + @Override + public ParameterState clearInParameters() { + if (hasInParameters()) { + if (abstractValue.isBottom()) { + return bottomPrimitiveTypeParameter(); + } + internalClearInParameters(); + } + assert !isEffectivelyBottom(); + return this; + } + + @Override + public ParameterState mutableCopy() { + return new ConcretePrimitiveTypeParameterState(abstractValue, copyInParameters()); } public ParameterState mutableJoin( AppView<AppInfoWithLiveness> appView, ConcretePrimitiveTypeParameterState parameterState, + DexType parameterType, Action onChangedAction) { + assert parameterType.isPrimitiveType(); boolean allowNullOrAbstractValue = false; boolean allowNonConstantNumbers = false; AbstractValue oldAbstractValue = abstractValue; @@ -50,7 +79,7 @@ } @Override - public AbstractValue getAbstractValue() { + public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) { return abstractValue; } @@ -59,6 +88,14 @@ return ConcreteParameterStateKind.PRIMITIVE; } + public boolean isEffectivelyBottom() { + return abstractValue.isBottom() && !hasInParameters(); + } + + public boolean isEffectivelyUnknown() { + return abstractValue.isUnknown(); + } + @Override public boolean isPrimitiveParameter() { return true;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java index 145d29b..d7f7bbc 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
@@ -4,53 +4,72 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.type.DynamicType; +import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; +import java.util.Collections; +import java.util.Set; -public class ConcreteReceiverParameterState extends ConcreteParameterState { +public class ConcreteReceiverParameterState extends ConcreteReferenceTypeParameterState { private DynamicType dynamicType; public ConcreteReceiverParameterState(DynamicType dynamicType) { - this.dynamicType = dynamicType; + this(dynamicType, Collections.emptySet()); } - public ParameterState mutableJoin( - ConcreteReceiverParameterState parameterState, Action onChangedAction) { - // TODO(b/190154391): Join the dynamic types using SubtypingInfo. - // TODO(b/190154391): Take in the static type as an argument, and unset the dynamic type if it - // equals the static type. - DynamicType oldDynamicType = dynamicType; - dynamicType = - dynamicType.equals(parameterState.dynamicType) ? dynamicType : DynamicType.unknown(); - if (dynamicType.isUnknown()) { - return unknown(); + public ConcreteReceiverParameterState( + DynamicType dynamicType, Set<MethodParameter> inParameters) { + super(inParameters); + this.dynamicType = dynamicType; + assert !isEffectivelyBottom() : "Must use BottomReceiverParameterState instead"; + assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead"; + } + + @Override + public ParameterState clearInParameters() { + if (hasInParameters()) { + if (dynamicType.isBottom()) { + return bottomReceiverParameter(); + } + internalClearInParameters(); } - boolean inParametersChanged = mutableJoinInParameters(parameterState); - if (widenInParameters()) { - return unknown(); - } - if (dynamicType != oldDynamicType || inParametersChanged) { - onChangedAction.execute(); - } + assert !isEffectivelyBottom(); return this; } @Override - public AbstractValue getAbstractValue() { + public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) { return AbstractValue.unknown(); } + @Override public DynamicType getDynamicType() { return dynamicType; } @Override + public Nullability getNullability() { + return getDynamicType().getDynamicUpperBoundType().nullability(); + } + + @Override public ConcreteParameterStateKind getKind() { return ConcreteParameterStateKind.RECEIVER; } + public boolean isEffectivelyBottom() { + return dynamicType.isBottom() && !hasInParameters(); + } + + public boolean isEffectivelyUnknown() { + return dynamicType.isUnknown(); + } + @Override public boolean isReceiverParameter() { return true; @@ -60,4 +79,33 @@ public ConcreteReceiverParameterState asReceiverParameter() { return this; } + + @Override + public ParameterState mutableCopy() { + return new ConcreteReceiverParameterState(dynamicType, copyInParameters()); + } + + @Override + public ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ConcreteReferenceTypeParameterState parameterState, + DexType parameterType, + Action onChangedAction) { + // TODO(b/190154391): Always take in the static type as an argument, and unset the dynamic type + // if it equals the static type. + assert parameterType == null || parameterType.isClassType(); + DynamicType oldDynamicType = dynamicType; + dynamicType = dynamicType.join(appView, parameterState.getDynamicType()); + if (dynamicType.isUnknown()) { + return unknown(); + } + boolean inParametersChanged = mutableJoinInParameters(parameterState); + if (widenInParameters()) { + return unknown(); + } + if (!dynamicType.equals(oldDynamicType) || inParametersChanged) { + onChangedAction.execute(); + } + return this; + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeParameterState.java new file mode 100644 index 0000000..30506a0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeParameterState.java
@@ -0,0 +1,40 @@ +// 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.optimize.argumentpropagation.codescanner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.DynamicType; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Action; +import java.util.Set; + +public abstract class ConcreteReferenceTypeParameterState extends ConcreteParameterState { + + ConcreteReferenceTypeParameterState(Set<MethodParameter> inParameters) { + super(inParameters); + } + + public abstract DynamicType getDynamicType(); + + public abstract Nullability getNullability(); + + @Override + public boolean isReferenceParameter() { + return true; + } + + @Override + public ConcreteReferenceTypeParameterState asReferenceParameter() { + return this; + } + + public abstract ParameterState mutableJoin( + AppView<AppInfoWithLiveness> appView, + ConcreteReferenceTypeParameterState parameterState, + DexType parameterType, + Action onChangedAction); +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java index 5bc6049..0de2d3e 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
@@ -5,8 +5,9 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import java.util.function.Supplier; +import java.util.function.Function; public interface MethodState { @@ -28,10 +29,25 @@ ConcreteMonomorphicMethodState asMonomorphic(); + ConcreteMonomorphicMethodStateOrBottom asMonomorphicOrBottom(); + + boolean isPolymorphic(); + + ConcretePolymorphicMethodState asPolymorphic(); + + ConcretePolymorphicMethodStateOrBottom asPolymorphicOrBottom(); + boolean isUnknown(); - MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState); + MethodState mutableCopy(); MethodState mutableJoin( - AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier); + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + MethodState methodState); + + MethodState mutableJoin( + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + Function<MethodState, MethodState> methodStateSupplier); }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java index 936210f..1b1800d 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java
@@ -40,6 +40,26 @@ } @Override + public ConcreteMonomorphicMethodStateOrBottom asMonomorphicOrBottom() { + return null; + } + + @Override + public boolean isPolymorphic() { + return false; + } + + @Override + public ConcretePolymorphicMethodState asPolymorphic() { + return null; + } + + @Override + public ConcretePolymorphicMethodStateOrBottom asPolymorphicOrBottom() { + return null; + } + + @Override public boolean isUnknown() { return false; }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java index c8b2456..50f02c9 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
@@ -5,10 +5,13 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Timing; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.function.Supplier; abstract class MethodStateCollection<K> { @@ -16,11 +19,14 @@ private final Map<K, MethodState> methodStates; MethodStateCollection(Map<K, MethodState> methodStates) { + assert methodStates.values().stream().noneMatch(MethodState::isBottom); this.methodStates = methodStates; } abstract K getKey(ProgramMethod method); + abstract DexMethodSignature getSignature(K method); + public void addMethodState( AppView<AppInfoWithLiveness> appView, ProgramMethod method, MethodState methodState) { addMethodState(appView, getKey(method), methodState); @@ -34,10 +40,15 @@ methodStates.compute( method, (ignore, existingMethodState) -> { + MethodState newMethodState; if (existingMethodState == null) { - return methodState; + newMethodState = methodState.mutableCopy(); + } else { + newMethodState = + existingMethodState.mutableJoin(appView, getSignature(method), methodState); } - return existingMethodState.mutableJoin(appView, methodState); + assert !newMethodState.isBottom(); + return newMethodState; }); } } @@ -46,22 +57,26 @@ * This intentionally takes a {@link Supplier<MethodState>} to avoid computing the method state * for a given call site when nothing is known about the arguments of the method. */ - public void addMethodState( + public void addTemporaryMethodState( AppView<AppInfoWithLiveness> appView, - ProgramMethod method, - Supplier<MethodState> methodStateSupplier) { - addMethodState(appView, getKey(method), methodStateSupplier); - } - - public void addMethodState( - AppView<AppInfoWithLiveness> appView, K method, Supplier<MethodState> methodStateSupplier) { + K method, + Function<MethodState, MethodState> methodStateSupplier, + Timing timing) { methodStates.compute( method, (ignore, existingMethodState) -> { if (existingMethodState == null) { - return methodStateSupplier.get(); + MethodState newMethodState = methodStateSupplier.apply(MethodState.bottom()); + assert !newMethodState.isBottom(); + return newMethodState; } - return existingMethodState.mutableJoin(appView, methodStateSupplier); + assert !existingMethodState.isBottom(); + timing.begin("Join temporary method state"); + MethodState joinResult = + existingMethodState.mutableJoin(appView, getSignature(method), methodStateSupplier); + assert !joinResult.isBottom(); + timing.end(); + return joinResult; }); } @@ -76,7 +91,11 @@ } public MethodState get(ProgramMethod method) { - return methodStates.getOrDefault(getKey(method), MethodState.bottom()); + return get(getKey(method)); + } + + public MethodState get(K method) { + return methodStates.getOrDefault(method, MethodState.bottom()); } public boolean isEmpty() { @@ -84,11 +103,23 @@ } public MethodState remove(ProgramMethod method) { + return removeOrElse(method, MethodState.bottom()); + } + + public MethodState removeOrElse(ProgramMethod method, MethodState defaultValue) { MethodState removed = methodStates.remove(getKey(method)); - return removed != null ? removed : MethodState.bottom(); + return removed != null ? removed : defaultValue; } public void set(ProgramMethod method, MethodState methodState) { - methodStates.put(getKey(method), methodState); + set(getKey(method), methodState); + } + + private void set(K method, MethodState methodState) { + if (methodState.isBottom()) { + methodStates.remove(method); + } else { + methodStates.put(method, methodState); + } } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java index ea48bfb..a715205 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.graph.ProgramMethod; import java.util.IdentityHashMap; import java.util.Map; @@ -28,4 +29,9 @@ DexMethod getKey(ProgramMethod method) { return method.getReference(); } + + @Override + DexMethodSignature getSignature(DexMethod method) { + return method.getSignature(); + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java index 2113be0..a244616 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java
@@ -28,4 +28,9 @@ DexMethodSignature getKey(ProgramMethod method) { return method.getMethodSignature(); } + + @Override + DexMethodSignature getSignature(DexMethodSignature method) { + return method; + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyParameterState.java new file mode 100644 index 0000000..68e0842 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyParameterState.java
@@ -0,0 +1,13 @@ +// 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.optimize.argumentpropagation.codescanner; + +public abstract class NonEmptyParameterState extends ParameterState { + + @Override + public NonEmptyParameterState asNonEmpty() { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java index 2c9a205..ad7b97c 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
@@ -5,17 +5,38 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; public abstract class ParameterState { + public static BottomParameterState bottomArrayTypeParameter() { + return BottomArrayTypeParameterState.get(); + } + + public static BottomParameterState bottomClassTypeParameter() { + return BottomClassTypeParameterState.get(); + } + + public static BottomParameterState bottomPrimitiveTypeParameter() { + return BottomPrimitiveTypeParameterState.get(); + } + + public static BottomParameterState bottomReceiverParameter() { + return BottomReceiverParameterState.get(); + } + public static UnknownParameterState unknown() { return UnknownParameterState.get(); } - public abstract AbstractValue getAbstractValue(); + public abstract AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView); + + public boolean isBottom() { + return false; + } public boolean isConcrete() { return false; @@ -25,15 +46,24 @@ return null; } + public NonEmptyParameterState asNonEmpty() { + return null; + } + public boolean isUnknown() { return false; } + public abstract ParameterState mutableCopy(); + public final ParameterState mutableJoin( - AppView<AppInfoWithLiveness> appView, ParameterState parameterState) { - return mutableJoin(appView, parameterState, Action.empty()); + AppView<AppInfoWithLiveness> appView, ParameterState parameterState, DexType parameterType) { + return mutableJoin(appView, parameterState, parameterType, Action.empty()); } public abstract ParameterState mutableJoin( - AppView<AppInfoWithLiveness> appView, ParameterState parameterState, Action onChangedAction); + AppView<AppInfoWithLiveness> appView, + ParameterState parameterState, + DexType parameterType, + Action onChangedAction); }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java index 1cfe728..b4bb41f 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
@@ -5,12 +5,13 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import java.util.function.Supplier; +import java.util.function.Function; // Use this when the nothing is known. public class UnknownMethodState extends MethodStateBase - implements ConcreteMonomorphicMethodStateOrUnknown { + implements ConcreteMonomorphicMethodStateOrUnknown, ConcretePolymorphicMethodStateOrUnknown { private static final UnknownMethodState INSTANCE = new UnknownMethodState(); @@ -26,13 +27,23 @@ } @Override - public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) { + public UnknownMethodState mutableCopy() { return this; } @Override public MethodState mutableJoin( - AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) { + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + MethodState methodState) { + return this; + } + + @Override + public MethodState mutableJoin( + AppView<AppInfoWithLiveness> appView, + DexMethodSignature methodSignature, + Function<MethodState, MethodState> methodStateSupplier) { return this; } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java index bfc835d..bcb5317 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
@@ -5,11 +5,12 @@ package com.android.tools.r8.optimize.argumentpropagation.codescanner; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; -public class UnknownParameterState extends ParameterState { +public class UnknownParameterState extends NonEmptyParameterState { private static final UnknownParameterState INSTANCE = new UnknownParameterState(); @@ -20,7 +21,7 @@ } @Override - public AbstractValue getAbstractValue() { + public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) { return AbstractValue.unknown(); } @@ -30,8 +31,16 @@ } @Override + public ParameterState mutableCopy() { + return this; + } + + @Override public ParameterState mutableJoin( - AppView<AppInfoWithLiveness> appView, ParameterState parameterState, Action onChangedAction) { + AppView<AppInfoWithLiveness> appView, + ParameterState parameterState, + DexType parameterType, + Action onChangedAction) { return this; } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java new file mode 100644 index 0000000..5d5399d --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
@@ -0,0 +1,134 @@ +// 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.optimize.argumentpropagation.codescanner; + +import static com.android.tools.r8.utils.MapUtils.ignoreKey; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexMethodSignature; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorCodeScanner; +import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +public class VirtualRootMethodsAnalysis extends DepthFirstTopDownClassHierarchyTraversal { + + static class VirtualRootMethod { + + private final ProgramMethod root; + private final ProgramMethodSet overrides = ProgramMethodSet.create(); + + VirtualRootMethod(ProgramMethod root) { + this.root = root; + } + + void addOverride(ProgramMethod override) { + assert override != root; + assert override.getMethodSignature().equals(root.getMethodSignature()); + overrides.add(override); + } + + ProgramMethod getRoot() { + return root; + } + + void forEach(Consumer<ProgramMethod> consumer) { + consumer.accept(root); + overrides.forEach(consumer); + } + + boolean hasOverrides() { + return !overrides.isEmpty(); + } + } + + private final Map<DexProgramClass, Map<DexMethodSignature, VirtualRootMethod>> + virtualRootMethodsPerClass = new IdentityHashMap<>(); + + private final Set<DexMethod> monomorphicVirtualMethods = Sets.newIdentityHashSet(); + + private final Map<DexMethod, DexMethod> virtualRootMethods = new IdentityHashMap<>(); + + public VirtualRootMethodsAnalysis( + AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { + super(appView, immediateSubtypingInfo); + } + + public void extendVirtualRootMethods( + Collection<DexProgramClass> stronglyConnectedComponent, + ArgumentPropagatorCodeScanner codeScanner) { + // Find all the virtual root methods in the strongly connected component. + run(stronglyConnectedComponent); + + // Commit the result to the code scanner. + codeScanner.addMonomorphicVirtualMethods(monomorphicVirtualMethods); + codeScanner.addVirtualRootMethods(virtualRootMethods); + } + + @Override + public void visit(DexProgramClass clazz) { + Map<DexMethodSignature, VirtualRootMethod> state = computeVirtualRootMethodsState(clazz); + virtualRootMethodsPerClass.put(clazz, state); + } + + private Map<DexMethodSignature, VirtualRootMethod> computeVirtualRootMethodsState( + DexProgramClass clazz) { + Map<DexMethodSignature, VirtualRootMethod> virtualRootMethodsForClass = new HashMap<>(); + immediateSubtypingInfo.forEachImmediateProgramSuperClass( + clazz, + superclass -> { + Map<DexMethodSignature, VirtualRootMethod> virtualRootMethodsForSuperclass = + virtualRootMethodsPerClass.get(superclass); + virtualRootMethodsForSuperclass.forEach( + (signature, info) -> + virtualRootMethodsForClass.computeIfAbsent(signature, ignoreKey(() -> info))); + }); + clazz.forEachProgramVirtualMethod( + method -> { + DexMethodSignature signature = method.getMethodSignature(); + if (virtualRootMethodsForClass.containsKey(signature)) { + virtualRootMethodsForClass.get(signature).addOverride(method); + } else { + virtualRootMethodsForClass.put(signature, new VirtualRootMethod(method)); + } + }); + return virtualRootMethodsForClass; + } + + @Override + public void prune(DexProgramClass clazz) { + // Record the overrides for each virtual method that is rooted at this class. + Map<DexMethodSignature, VirtualRootMethod> virtualRootMethodsForClass = + virtualRootMethodsPerClass.remove(clazz); + clazz.forEachProgramVirtualMethod( + rootCandidate -> { + VirtualRootMethod virtualRootMethod = + virtualRootMethodsForClass.remove(rootCandidate.getMethodSignature()); + if (!rootCandidate.isStructurallyEqualTo(virtualRootMethod.getRoot())) { + return; + } + boolean isMonomorphicVirtualMethod = + !clazz.isInterface() && !virtualRootMethod.hasOverrides(); + if (isMonomorphicVirtualMethod) { + monomorphicVirtualMethods.add(rootCandidate.getReference()); + } else { + virtualRootMethod.forEach( + method -> + virtualRootMethods.put(method.getReference(), rootCandidate.getReference())); + } + }); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java index cba390e..b551b68 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState; @@ -17,6 +18,7 @@ import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyParameterState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Action; @@ -91,9 +93,12 @@ private void propagate( ParameterNode parameterNode, Consumer<ParameterNode> affectedNodeConsumer) { ParameterState parameterState = parameterNode.getState(); + if (parameterState.isBottom()) { + return; + } for (ParameterNode successorNode : parameterNode.getSuccessors()) { successorNode.addState( - appView, parameterState, () -> affectedNodeConsumer.accept(successorNode)); + appView, parameterState.asNonEmpty(), () -> affectedNodeConsumer.accept(successorNode)); } } @@ -112,12 +117,20 @@ return; } assert methodState.isMonomorphic(); + boolean allUnknown = true; for (ParameterState parameterState : methodState.asMonomorphic().getParameterStates()) { - if (!parameterState.isUnknown()) { + if (parameterState.isBottom()) { + methodStates.set(method, MethodState.bottom()); return; } + if (!parameterState.isUnknown()) { + assert parameterState.isConcrete(); + allUnknown = false; + } } - methodStates.set(method, MethodState.unknown()); + if (allUnknown) { + methodStates.set(method, MethodState.unknown()); + } } private class FlowGraph { @@ -145,8 +158,7 @@ } // Add nodes for the parameters for which we have non-trivial information. - ConcreteMonomorphicMethodState monomorphicMethodState = - methodState.asConcrete().asMonomorphic(); + ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic(); List<ParameterState> parameterStates = monomorphicMethodState.getParameterStates(); for (int parameterIndex = 0; parameterIndex < parameterStates.size(); parameterIndex++) { ParameterState parameterState = parameterStates.get(parameterIndex); @@ -159,8 +171,9 @@ int parameterIndex, ConcreteMonomorphicMethodState methodState, ParameterState parameterState) { - // No need to create nodes for parameters we don't know anything about. - if (parameterState.isUnknown()) { + // No need to create nodes for parameters with no in-parameters and parameters we don't know + // anything about. + if (parameterState.isBottom() || parameterState.isUnknown()) { return; } @@ -172,10 +185,10 @@ return; } - ParameterNode node = - getOrCreateParameterNode(method.getReference(), parameterIndex, methodState); + ParameterNode node = getOrCreateParameterNode(method, parameterIndex, methodState); for (MethodParameter inParameter : concreteParameterState.getInParameters()) { - MethodState enclosingMethodState = getEnclosingMethodStateForParameter(inParameter); + ProgramMethod enclosingMethod = getEnclosingMethod(inParameter); + MethodState enclosingMethodState = getMethodState(enclosingMethod); if (enclosingMethodState.isBottom()) { // The current method is called from a dead method; no need to propagate any information // from the dead call site. @@ -194,32 +207,38 @@ ParameterNode predecessor = getOrCreateParameterNode( - inParameter.getMethod(), + enclosingMethod, inParameter.getIndex(), enclosingMethodState.asConcrete().asMonomorphic()); node.addPredecessor(predecessor); } - concreteParameterState.clearInParameters(); + + if (!node.getState().isUnknown()) { + assert node.getState() == concreteParameterState; + node.setState(concreteParameterState.clearInParameters()); + } } private ParameterNode getOrCreateParameterNode( - DexMethod key, int parameterIndex, ConcreteMonomorphicMethodState methodState) { + ProgramMethod method, int parameterIndex, ConcreteMonomorphicMethodState methodState) { Int2ReferenceMap<ParameterNode> parameterNodesForMethod = - nodes.computeIfAbsent(key, ignoreKey(Int2ReferenceOpenHashMap::new)); + nodes.computeIfAbsent(method.getReference(), ignoreKey(Int2ReferenceOpenHashMap::new)); return parameterNodesForMethod.compute( parameterIndex, (ignore, parameterNode) -> parameterNode != null ? parameterNode - : new ParameterNode(methodState, parameterIndex)); + : new ParameterNode( + methodState, parameterIndex, method.getArgumentType(parameterIndex))); } - private MethodState getEnclosingMethodStateForParameter(MethodParameter methodParameter) { + private ProgramMethod getEnclosingMethod(MethodParameter methodParameter) { DexMethod methodReference = methodParameter.getMethod(); - ProgramMethod method = - methodReference.lookupOnProgramClass( - asProgramClassOrNull( - appView.definitionFor(methodParameter.getMethod().getHolderType()))); + return methodReference.lookupOnProgramClass( + asProgramClassOrNull(appView.definitionFor(methodParameter.getMethod().getHolderType()))); + } + + private MethodState getMethodState(ProgramMethod method) { if (method == null) { // Conservatively return unknown if for some reason we can't find the method. assert false; @@ -233,15 +252,18 @@ private final ConcreteMonomorphicMethodState methodState; private final int parameterIndex; + private final DexType parameterType; private final Set<ParameterNode> predecessors = Sets.newIdentityHashSet(); private final Set<ParameterNode> successors = Sets.newIdentityHashSet(); private boolean pending = true; - ParameterNode(ConcreteMonomorphicMethodState methodState, int parameterIndex) { + ParameterNode( + ConcreteMonomorphicMethodState methodState, int parameterIndex, DexType parameterType) { this.methodState = methodState; this.parameterIndex = parameterIndex; + this.parameterType = parameterType; } void addPredecessor(ParameterNode predecessor) { @@ -274,11 +296,12 @@ void addState( AppView<AppInfoWithLiveness> appView, - ParameterState parameterStateToAdd, + NonEmptyParameterState parameterStateToAdd, Action onChangedAction) { ParameterState oldParameterState = getState(); ParameterState newParameterState = - oldParameterState.mutableJoin(appView, parameterStateToAdd, onChangedAction); + oldParameterState.mutableJoin( + appView, parameterStateToAdd, parameterType, onChangedAction); if (newParameterState != oldParameterState) { setState(newParameterState); onChangedAction.execute();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java index 69cbb7b..72990cb 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -6,11 +6,15 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; -import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; +import com.android.tools.r8.graph.MethodResolutionResult; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.ClassTypeElement; +import com.android.tools.r8.ir.analysis.type.DynamicType; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionBySignature; @@ -121,21 +125,12 @@ subclass -> interfaceState.forEach( (interfaceMethod, interfaceMethodState) -> { - // TODO(b/190154391): Change resolution to take a signature. - DexMethod interfaceMethodToResolve = - appView - .dexItemFactory() - .createMethod( - subclass.getType(), - interfaceMethod.getProto(), - interfaceMethod.getName()); - SingleResolutionResult resolutionResult = - appView - .appInfo() - .resolveMethodOnClass(interfaceMethodToResolve, subclass) - .asSingleResolution(); - if (resolutionResult == null) { - assert false; + MethodResolutionResult resolutionResult = + appView.appInfo().resolveMethodOnClass(interfaceMethod, subclass); + if (resolutionResult.isFailedResolution()) { + // TODO(b/190154391): Do we need to propagate argument information to the first + // virtual method above the inaccessible method in the class hierarchy? + assert resolutionResult.isIllegalAccessErrorResult(subclass, appView.appInfo()); return; } @@ -146,10 +141,66 @@ return; } - methodStates.addMethodState(appView, resolvedMethod, interfaceMethodState); + MethodState transformedInterfaceMethodState = + transformInterfaceMethodStateForClassMethod( + subclass, resolvedMethod, interfaceMethodState); + if (!transformedInterfaceMethodState.isBottom()) { + methodStates.addMethodState( + appView, resolvedMethod, transformedInterfaceMethodState); + } })); } + private MethodState transformInterfaceMethodStateForClassMethod( + DexProgramClass clazz, ProgramMethod resolvedMethod, MethodState methodState) { + if (!methodState.isPolymorphic()) { + return methodState.mutableCopy(); + } + + // Rewrite the bounds of the polymorphic method state. If a given piece of argument information + // should be propagated to the resolved method, we replace the type bounds by the holder of the + // resolved method. + ConcretePolymorphicMethodState polymorphicMethodState = methodState.asPolymorphic(); + MethodState rewrittenMethodState = + polymorphicMethodState.mutableCopyWithRewrittenBounds( + appView, + bounds -> { + boolean shouldPropagateMethodStateForBounds; + if (bounds.isUnknown()) { + shouldPropagateMethodStateForBounds = true; + } else { + ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType(); + shouldPropagateMethodStateForBounds = + upperBound + .getInterfaces() + .anyMatch( + (interfaceType, isKnown) -> + appView.appInfo().isSubtype(clazz.getType(), interfaceType)); + } + if (shouldPropagateMethodStateForBounds) { + return DynamicType.createExact( + TypeElement.fromDexType( + resolvedMethod.getHolderType(), Nullability.maybeNull(), appView) + .asClassType()); + } + return null; + }, + resolvedMethod.getMethodSignature()); + + // If the resolved method is a virtual method that does not override any methods and are not + // overridden by any methods, then we use a monomorphic method state for it. Therefore, we + // transform this polymorphic method state into a monomorphic method state, before joining it + // into the method's state. + if (methodStates.get(resolvedMethod).isMonomorphic() && rewrittenMethodState.isPolymorphic()) { + ConcretePolymorphicMethodState rewrittenPolymorphicMethodState = + rewrittenMethodState.asPolymorphic(); + assert rewrittenPolymorphicMethodState.values().size() == 1; + return rewrittenPolymorphicMethodState.values().iterator().next(); + } + + return rewrittenMethodState; + } + private boolean verifyAllInterfacesFinished( Collection<DexProgramClass> stronglyConnectedComponent) { assert stronglyConnectedComponent.stream()
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java index 771a718..6df20f0 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -4,13 +4,16 @@ package com.android.tools.r8.optimize.argumentpropagation.propagation; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; import static com.android.tools.r8.utils.MapUtils.ignoreKey; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; +import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.DynamicType; @@ -20,6 +23,7 @@ import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionBySignature; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.Collection; import java.util.HashMap; @@ -80,7 +84,7 @@ // Add the argument information that is inactive until a given upper bound. parentState.inactiveUntilUpperBound.forEach( - (bounds, inactiveMethodState) -> { + (bounds, inactiveMethodStates) -> { ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType(); if (upperBound.equalUpToNullability(classType)) { // The upper bound is the current class, thus this inactive information now becomes @@ -90,10 +94,38 @@ .computeIfAbsent( bounds.getDynamicLowerBoundType().getClassType(), ignoreKey(MethodStateCollectionBySignature::create)) - .addMethodStates(appView, inactiveMethodState); + .addMethodStates(appView, inactiveMethodStates); } else { - active.addMethodStates(appView, inactiveMethodState); + active.addMethodStates(appView, inactiveMethodStates); } + + inactiveMethodStates.forEach( + (signature, methodState) -> { + SingleResolutionResult resolutionResult = + appView.appInfo().resolveMethodOn(clazz, signature).asSingleResolution(); + + // Find the first virtual method in the super class hierarchy. + while (resolutionResult != null + && resolutionResult.getResolvedMethod().belongsToDirectPool()) { + resolutionResult = + appView + .appInfo() + .resolveMethodOnClass( + signature, resolutionResult.getResolvedHolder().getSuperType()) + .asSingleResolution(); + } + + // Propagate the argument information to the method on the super class. + if (resolutionResult != null + && resolutionResult.getResolvedHolder().isProgramClass() + && resolutionResult.getResolvedHolder() != clazz) { + propagationStates + .get(resolutionResult.getResolvedHolder().asProgramClass()) + .active + .addMethodState( + appView, resolutionResult.getResolvedProgramMethod(), methodState); + } + }); } else { // Still inactive. // TODO(b/190154391): Only carry this information downwards if the upper bound is a @@ -101,16 +133,19 @@ // although clearly the information will never become active. inactiveUntilUpperBound .computeIfAbsent(bounds, ignoreKey(MethodStateCollectionBySignature::create)) - .addMethodStates(appView, inactiveMethodState); + .addMethodStates(appView, inactiveMethodStates); } }); } private MethodState computeMethodStateForPolymorhicMethod(ProgramMethod method) { assert method.getDefinition().isNonPrivateVirtualMethod(); - MethodState methodState = active.get(method); - for (MethodStateCollectionBySignature methodStates : activeUntilLowerBound.values()) { - methodState = methodState.mutableJoin(appView, methodStates.get(method)); + MethodState methodState = active.get(method).mutableCopy(); + if (!activeUntilLowerBound.isEmpty()) { + DexMethodSignature methodSignature = method.getMethodSignature(); + for (MethodStateCollectionBySignature methodStates : activeUntilLowerBound.values()) { + methodState = methodState.mutableJoin(appView, methodSignature, methodStates.get(method)); + } } return methodState; } @@ -141,13 +176,10 @@ @Override public void visit(DexProgramClass clazz) { assert !propagationStates.containsKey(clazz); - PropagationState propagationState = computePropagationState(clazz); - computeFinalMethodStates(clazz, propagationState); + computePropagationState(clazz); } - private PropagationState computePropagationState(DexProgramClass clazz) { - ClassTypeElement classType = - TypeElement.fromDexType(clazz.getType(), maybeNull(), appView).asClassType(); + private void computePropagationState(DexProgramClass clazz) { PropagationState propagationState = new PropagationState(clazz); // Join the argument information from the methods of the current class. @@ -162,7 +194,7 @@ // distinguish monomorphic unknown method states from polymorphic unknown method states. // We only need to propagate polymorphic unknown method states here. if (methodState.isUnknown()) { - propagationState.active.addMethodState(appView, method, methodState); + propagationState.active.set(method, UnknownMethodState.get()); return; } @@ -183,7 +215,7 @@ // TODO(b/190154391): Verify that the bounds are not trivial according to the // static receiver type. ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType(); - if (upperBound.equalUpToNullability(classType)) { + if (isUpperBoundSatisfied(upperBound, clazz, propagationState)) { if (bounds.hasDynamicLowerBoundType()) { // TODO(b/190154391): Verify that the lower bound is a subtype of the current // class. @@ -197,7 +229,10 @@ propagationState.active.addMethodState(appView, method, methodStateForBounds); } } else { - assert !classType.lessThanOrEqualUpToNullability(upperBound, appView); + assert !clazz + .getType() + .toTypeElement(appView) + .lessThanOrEqualUpToNullability(upperBound, appView); propagationState .inactiveUntilUpperBound .computeIfAbsent( @@ -209,11 +244,30 @@ }); propagationStates.put(clazz, propagationState); - return propagationState; + } + + private boolean isUpperBoundSatisfied( + ClassTypeElement upperBound, + DexProgramClass currentClass, + PropagationState propagationState) { + DexType upperBoundType = + upperBound.getClassType() == appView.dexItemFactory().objectType + && upperBound.getInterfaces().hasSingleKnownInterface() + ? upperBound.getInterfaces().getSingleKnownInterface() + : upperBound.getClassType(); + DexProgramClass upperBoundClass = asProgramClassOrNull(appView.definitionFor(upperBoundType)); + if (upperBoundClass == null) { + // We should generally never have a dynamic receiver upper bound for a program method which is + // not a program class. However, since the program may not type change or there could be + // missing classes, we still need to cover this case. In the rare cases where this happens, we + // conservatively consider the upper bound to be satisfied. + return true; + } + return upperBoundClass == currentClass; } private void computeFinalMethodStates(DexProgramClass clazz, PropagationState propagationState) { - clazz.forEachProgramMethod(method -> computeFinalMethodState(method, propagationState)); + clazz.forEachProgramVirtualMethod(method -> computeFinalMethodState(method, propagationState)); } private void computeFinalMethodState(ProgramMethod method, PropagationState propagationState) { @@ -223,19 +277,23 @@ } MethodState methodState = methodStates.get(method); - - // If this is a polymorphic method, we need to compute the method state to account for dynamic - // dispatch. - if (methodState.isConcrete() && methodState.asConcrete().isPolymorphic()) { - methodState = propagationState.computeMethodStateForPolymorhicMethod(method); - assert !methodState.isConcrete() || methodState.asConcrete().isMonomorphic(); - methodStates.set(method, methodState); + if (methodState.isMonomorphic() || methodState.isUnknown()) { + return; } + + assert methodState.isBottom() || methodState.isPolymorphic(); + + // This is a polymorphic method and we need to compute the method state to account for dynamic + // dispatch. + methodState = propagationState.computeMethodStateForPolymorhicMethod(method); + assert !methodState.isConcrete() || methodState.asConcrete().isMonomorphic(); + methodStates.set(method, methodState); } @Override public void prune(DexProgramClass clazz) { - propagationStates.remove(clazz); + PropagationState propagationState = propagationStates.remove(clazz); + computeFinalMethodStates(clazz, propagationState); } private boolean verifyAllClassesFinished(Collection<DexProgramClass> stronglyConnectedComponent) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java index 379efaf..14d47ac 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
@@ -102,13 +102,12 @@ assert newlySeenButNotFinishedRoots.stream() .allMatch( newlySeenButNotFinishedRoot -> { - assert newlySeenButNotFinishedRoot.isInterface(); assert isRoot(newlySeenButNotFinishedRoot); assert isClassSeenButNotFinished(newlySeenButNotFinishedRoot); return true; }); - // Prioritize this interface over other not yet seen interfaces. This leads to more efficient - // state pruning. + // Prioritize this class over other not yet seen classes. This leads to more efficient state + // pruning. roots.addAll(newlySeenButNotFinishedRoots); newlySeenButNotFinishedRoots.clear(); } @@ -122,7 +121,7 @@ // Before continuing the top-down traversal, ensure that all super interfaces are processed, // but without visiting the entire subtree of each super interface. if (!isClassSeenButNotFinished(clazz)) { - processImplementedInterfaces(clazz); + processSuperClasses(clazz); processClass(clazz); } @@ -130,24 +129,22 @@ markFinished(clazz); } - private void processImplementedInterfaces(DexProgramClass interfaceDefinition) { - assert !isClassSeenButNotFinished(interfaceDefinition); - assert !isClassFinished(interfaceDefinition); - for (DexType implementedType : interfaceDefinition.getInterfaces()) { - DexProgramClass implementedDefinition = - asProgramClassOrNull(appView.definitionFor(implementedType)); - if (implementedDefinition == null || isClassSeenButNotFinished(implementedDefinition)) { - continue; - } - assert isClassUnseen(implementedDefinition); - processImplementedInterfaces(implementedDefinition); - processClass(implementedDefinition); + private void processSuperClasses(DexProgramClass clazz) { + assert !isClassSeenButNotFinished(clazz); + assert !isClassFinished(clazz); + immediateSubtypingInfo.forEachImmediateProgramSuperClassMatching( + clazz, + superclass -> !isClassSeenButNotFinished(superclass), + superclass -> { + assert isClassUnseen(superclass); + processSuperClasses(superclass); + processClass(superclass); - // If this is a root, then record that this root is seen but not finished. - if (isRoot(implementedDefinition)) { - newlySeenButNotFinishedRoots.add(implementedDefinition); - } - } + // If this is a root, then record that this root is seen but not finished. + if (isRoot(superclass)) { + newlySeenButNotFinishedRoots.add(superclass); + } + }); } private void processSubclasses(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/StronglyConnectedProgramClasses.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/StronglyConnectedProgramClasses.java new file mode 100644 index 0000000..25d28cb --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/StronglyConnectedProgramClasses.java
@@ -0,0 +1,49 @@ +// 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.optimize.argumentpropagation.utils; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.WorkList; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class StronglyConnectedProgramClasses { + + /** + * Computes the strongly connected components in the program class hierarchy (where extends and + * implements edges are treated as bidirectional). + */ + public static List<Set<DexProgramClass>> computeStronglyConnectedProgramClasses( + AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { + Set<DexProgramClass> seen = Sets.newIdentityHashSet(); + List<Set<DexProgramClass>> stronglyConnectedComponents = new ArrayList<>(); + for (DexProgramClass clazz : appView.appInfo().classes()) { + if (seen.contains(clazz)) { + continue; + } + Set<DexProgramClass> stronglyConnectedComponent = + internalComputeStronglyConnectedProgramClasses(clazz, immediateSubtypingInfo); + stronglyConnectedComponents.add(stronglyConnectedComponent); + seen.addAll(stronglyConnectedComponent); + } + return stronglyConnectedComponents; + } + + private static Set<DexProgramClass> internalComputeStronglyConnectedProgramClasses( + DexProgramClass clazz, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { + WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(clazz); + while (worklist.hasNext()) { + DexProgramClass current = worklist.next(); + immediateSubtypingInfo.forEachImmediateProgramSuperClass(current, worklist::addIfNotSeen); + worklist.addIfNotSeen(immediateSubtypingInfo.getSubclasses(current)); + } + return worklist.getSeenSet(); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/WideningUtils.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/WideningUtils.java new file mode 100644 index 0000000..f30aa8e --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/WideningUtils.java
@@ -0,0 +1,88 @@ +// 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.optimize.argumentpropagation.utils; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ObjectAllocationInfoCollection; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.ClassTypeElement; +import com.android.tools.r8.ir.analysis.type.DynamicType; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.shaking.AppInfoWithLiveness; + +public class WideningUtils { + + public static DynamicType widenDynamicReceiverType( + AppView<AppInfoWithLiveness> appView, + ProgramMethod resolvedMethod, + DynamicType dynamicReceiverType) { + return shouldWidenDynamicType( + appView, + dynamicReceiverType, + resolvedMethod.getHolderType(), + Nullability.definitelyNotNull()) + ? DynamicType.unknown() + : dynamicReceiverType; + } + + public static DynamicType widenDynamicNonReceiverType( + AppView<AppInfoWithLiveness> appView, DynamicType dynamicType, DexType staticType) { + return widenDynamicNonReceiverType(appView, dynamicType, staticType, Nullability.maybeNull()); + } + + public static DynamicType widenDynamicNonReceiverType( + AppView<AppInfoWithLiveness> appView, + DynamicType dynamicType, + DexType staticType, + Nullability staticNullability) { + return shouldWidenDynamicType(appView, dynamicType, staticType, staticNullability) + ? DynamicType.unknown() + : dynamicType; + } + + private static boolean shouldWidenDynamicType( + AppView<AppInfoWithLiveness> appView, + DynamicType dynamicType, + DexType staticType, + Nullability staticNullability) { + assert staticType.isClassType(); + if (dynamicType.isUnknown()) { + return true; + } + if (dynamicType.isBottom() + || dynamicType.isNullType() + || dynamicType.getNullability().strictlyLessThan(staticNullability)) { + return false; + } + ClassTypeElement staticTypeElement = + staticType.toTypeElement(appView).asClassType().getOrCreateVariant(staticNullability); + if (!dynamicType.getDynamicUpperBoundType().equals(staticTypeElement)) { + return false; + } + if (!dynamicType.hasDynamicLowerBoundType()) { + return true; + } + + DexClass staticTypeClass = appView.definitionFor(staticType); + if (staticTypeClass == null || !staticTypeClass.isProgramClass()) { + // TODO(b/190154391): If this is a library class with no program subtypes, then we might as + // well widen to 'unknown'. + return false; + } + + // If the static type does not have any program subtypes, then widen the dynamic type to + // unknown. + // + // Note that if the static type is pinned, it could have subtypes outside the set of program + // classes, but in this case it is still unlikely that we can use the dynamic lower bound type + // information for anything, so we intentionally also widen to 'unknown' in this case. + ObjectAllocationInfoCollection objectAllocationInfoCollection = + appView.appInfo().getObjectAllocationInfoCollection(); + return !objectAllocationInfoCollection.hasInstantiatedStrictSubtype( + staticTypeClass.asProgramClass()); + } +}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java index 2e7b6b5..a0ac6e8 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -128,6 +128,7 @@ } MethodResolutionResult methodResult = resolutionResult.asMethodResolutionResult(); if (methodResult.isClassNotFoundResult() + || methodResult.isArrayCloneMethodResult() || methodResult.isNoSuchMethodErrorResult(context.getContextClass(), appInfo)) { return; }
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 ba6fb3b..41eb11b 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -6,8 +6,6 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO; import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod; -import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.emulateInterfaceLibraryMethod; -import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType; import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier; import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod; import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument; @@ -25,7 +23,6 @@ import com.android.tools.r8.experimental.graphinfo.GraphConsumer; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ClassAccessFlags; import com.android.tools.r8.graph.ClassDefinition; import com.android.tools.r8.graph.ClasspathOrLibraryClass; import com.android.tools.r8.graph.ClasspathOrLibraryDefinition; @@ -60,19 +57,16 @@ import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl; import com.android.tools.r8.graph.FieldAccessInfoImpl; import com.android.tools.r8.graph.FieldResolutionResult; -import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; import com.android.tools.r8.graph.GenericSignatureEnqueuerAnalysis; import com.android.tools.r8.graph.InnerClassAttribute; import com.android.tools.r8.graph.LookupLambdaTarget; import com.android.tools.r8.graph.LookupTarget; -import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.MethodAccessInfoCollection; import com.android.tools.r8.graph.MethodResolutionResult; import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult; import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; import com.android.tools.r8.graph.NestMemberClassAttribute; import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl; -import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.graph.ProgramDefinition; import com.android.tools.r8.graph.ProgramDerivedContext; import com.android.tools.r8.graph.ProgramField; @@ -128,7 +122,6 @@ import com.android.tools.r8.utils.Action; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.IteratorUtils; -import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.SetUtils; @@ -3555,9 +3548,6 @@ assert false; } } - if (mode.isInitialTreeShaking()) { - libraryClasses.addAll(synthesizeDesugaredLibraryClasses()); - } // Add just referenced non-program types. We can't replace the program classes at this point as // they are needed in tree pruning. @@ -3694,56 +3684,6 @@ return true; } - private List<DexLibraryClass> synthesizeDesugaredLibraryClasses() { - List<DexLibraryClass> synthesizedClasses = new ArrayList<>(); - Map<DexType, DexType> emulateLibraryInterface = - options.desugaredLibraryConfiguration.getEmulateLibraryInterface(); - emulateLibraryInterface - .keySet() - .forEach( - interfaceType -> { - DexClass interfaceClass = appView.definitionFor(interfaceType); - if (interfaceClass == null) { - appView - .reporter() - .error( - new StringDiagnostic( - "The interface " - + interfaceType.getTypeName() - + " is missing, but is required for Java 8+ API desugaring.")); - return; - } - - DexType emulateInterfaceType = - getEmulateLibraryInterfaceClassType(interfaceType, dexItemFactory); - assert appView.definitionFor(emulateInterfaceType) == null; - - List<DexEncodedMethod> emulateInterfaceClassMethods = - ListUtils.newArrayList( - builder -> - interfaceClass.forEachClassMethodMatching( - DexEncodedMethod::isDefaultMethod, - method -> - builder.accept( - new DexEncodedMethod( - emulateInterfaceLibraryMethod(method, dexItemFactory), - MethodAccessFlags.createPublicStaticSynthetic(), - MethodTypeSignature.noSignature(), - DexAnnotationSet.empty(), - ParameterAnnotationsList.empty(), - null, - true)))); - - synthesizedClasses.add( - DexLibraryClass.builder(dexItemFactory) - .setAccessFlags(ClassAccessFlags.createPublicFinalSynthetic()) - .setDirectMethods(emulateInterfaceClassMethods) - .setType(emulateInterfaceType) - .build()); - }); - return synthesizedClasses; - } - private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> Set<R> toDescriptorSet(Set<D> set) { ImmutableSet.Builder<R> builder = new ImmutableSet.Builder<>();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java index a86e143..71ae785 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -474,7 +474,44 @@ return legacyItem; } - private DexProgramClass internalCreateClass( + private DexProgramClass internalEnsureDexProgramClass( + SyntheticKind kind, + Consumer<SyntheticProgramClassBuilder> classConsumer, + Consumer<DexProgramClass> onCreationConsumer, + SynthesizingContext outerContext, + DexType type, + AppView<?> appView) { + // Fast path is that the synthetic is already present. If so it must be a program class. + DexClass dexClass = appView.definitionFor(type); + if (dexClass != null) { + assert dexClass.isProgramClass(); + return dexClass.asProgramClass(); + } + // Slow path creates the class using the context to make it thread safe. + synchronized (type) { + // Recheck if it is present now the lock is held. + dexClass = appView.definitionFor(type); + if (dexClass != null) { + assert dexClass.isProgramClass(); + return dexClass.asProgramClass(); + } + assert !isSyntheticClass(type); + DexProgramClass dexProgramClass = + internalCreateProgramClass( + kind, + syntheticProgramClassBuilder -> { + syntheticProgramClassBuilder.setUseSortedMethodBacking(true); + classConsumer.accept(syntheticProgramClassBuilder); + }, + outerContext, + type, + appView.dexItemFactory()); + onCreationConsumer.accept(dexProgramClass); + return dexProgramClass; + } + } + + private DexProgramClass internalCreateProgramClass( SyntheticKind kind, Consumer<SyntheticProgramClassBuilder> fn, SynthesizingContext outerContext, @@ -499,7 +536,7 @@ DexType type = SyntheticNaming.createInternalType( kind, outerContext, context.getSyntheticSuffix(), appView.dexItemFactory()); - return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory()); + return internalCreateProgramClass(kind, fn, outerContext, type, appView.dexItemFactory()); } // TODO(b/172194101): Make this take a unique context. @@ -510,7 +547,7 @@ Consumer<SyntheticProgramClassBuilder> fn) { SynthesizingContext outerContext = internalGetOuterContext(context, appView); DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory()); - return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory()); + return internalCreateProgramClass(kind, fn, outerContext, type, appView.dexItemFactory()); } public DexProgramClass getExistingFixedClass( @@ -547,36 +584,7 @@ assert kind.isFixedSuffixSynthetic; SynthesizingContext outerContext = internalGetOuterContext(context, appView); DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory()); - // Fast path is that the synthetic is already present. If so it must be a program class. - DexClass clazz = appView.definitionFor(type); - if (clazz != null) { - assert isSyntheticClass(type); - assert clazz.isProgramClass(); - return clazz.asProgramClass(); - } - // Slow path creates the class using the context to make it thread safe. - synchronized (context) { - // Recheck if it is present now the lock is held. - clazz = appView.definitionFor(type); - if (clazz != null) { - assert isSyntheticClass(type); - assert clazz.isProgramClass(); - return clazz.asProgramClass(); - } - assert !isSyntheticClass(type); - DexProgramClass dexProgramClass = - internalCreateClass( - kind, - syntheticProgramClassBuilder -> { - syntheticProgramClassBuilder.setUseSortedMethodBacking(true); - fn.accept(syntheticProgramClassBuilder); - }, - outerContext, - type, - appView.dexItemFactory()); - onCreationConsumer.accept(dexProgramClass); - return dexProgramClass; - } + return internalEnsureDexProgramClass(kind, fn, onCreationConsumer, outerContext, type, appView); } public ProgramMethod ensureFixedClassMethod( @@ -668,9 +676,11 @@ ClasspathOrLibraryClass context, AppView<?> appView, Consumer<SyntheticClasspathClassBuilder> buildClassCallback, + Consumer<DexClasspathClass> onClassCreationCallback, Consumer<SyntheticMethodBuilder> buildMethodCallback) { DexClasspathClass clazz = - ensureFixedClasspathClass(kind, context, appView, buildClassCallback, ignored -> {}); + ensureFixedClasspathClass( + kind, context, appView, buildClassCallback, onClassCreationCallback); DexMethod methodReference = appView.dexItemFactory().createMethod(clazz.getType(), methodProto, methodName); DexEncodedMethod methodDefinition = @@ -708,16 +718,17 @@ } } - public DexProgramClass createFixedClassFromType( + public DexProgramClass ensureFixedClassFromType( SyntheticKind kind, DexType contextType, - DexItemFactory factory, - Consumer<SyntheticProgramClassBuilder> fn) { + AppView<?> appView, + Consumer<SyntheticProgramClassBuilder> fn, + Consumer<DexProgramClass> onCreationConsumer) { // Obtain the outer synthesizing context in the case the context itself is synthetic. // This is to ensure a flat input-type -> synthetic-item mapping. SynthesizingContext outerContext = SynthesizingContext.fromType(contextType); - DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory); - return internalCreateClass(kind, fn, outerContext, type, factory); + DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory()); + return internalEnsureDexProgramClass(kind, fn, onCreationConsumer, outerContext, type, appView); } /** Create a single synthetic method item. */
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java index a66d383..9311d46 100644 --- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java +++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -51,6 +51,10 @@ this.ignoreDexInArchive = ignoreDexInArchive; } + public Origin getOrigin() { + return origin; + } + private List<ProgramResource> readArchive() throws IOException { List<ProgramResource> dexResources = new ArrayList<>(); List<ProgramResource> classResources = new ArrayList<>();
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 5ee21c1..f391fb1 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -50,6 +50,7 @@ import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.enums.EnumDataMap; import com.android.tools.r8.naming.MapVersion; +import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; import com.android.tools.r8.references.ClassReference; @@ -1484,6 +1485,9 @@ public static int NO_LIMIT = -1; + public ArgumentPropagatorEventConsumer argumentPropagatorEventConsumer = + ArgumentPropagatorEventConsumer.emptyConsumer(); + // Force writing the specified bytes as the DEX version content. public byte[] forceDexVersionBytes = null;
diff --git a/src/main/java/com/android/tools/r8/utils/LinkedHashSetUtils.java b/src/main/java/com/android/tools/r8/utils/LinkedHashSetUtils.java new file mode 100644 index 0000000..d7812fb --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/LinkedHashSetUtils.java
@@ -0,0 +1,14 @@ +// 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.utils; + +import java.util.LinkedHashSet; + +public class LinkedHashSetUtils { + + public static <T> void addAll(LinkedHashSet<T> set, LinkedHashSet<T> elements) { + set.addAll(elements); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java index 37deccc..9818d3e 100644 --- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java +++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -7,6 +7,8 @@ import static com.android.tools.r8.utils.ClassReferenceUtils.getClassReferenceComparator; import static com.android.tools.r8.utils.TypeReferenceUtils.getTypeReferenceComparator; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.references.ArrayReference; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.FieldReference; @@ -87,6 +89,15 @@ } } + public static DexMethod toDexMethod( + MethodReference methodReference, DexItemFactory dexItemFactory) { + return dexItemFactory.createMethod( + ClassReferenceUtils.toDexType(methodReference.getHolderClass(), dexItemFactory), + TypeReferenceUtils.toDexProto( + methodReference.getFormalTypes(), methodReference.getReturnType(), dexItemFactory), + methodReference.getMethodName()); + } + public static String toSourceStringWithoutHolderAndReturnType(MethodReference methodReference) { return toSourceString(methodReference, false, false); }
diff --git a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java index 9d322e6..a5145f4 100644 --- a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java +++ b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
@@ -37,6 +37,15 @@ return COMPARATOR; } + public static DexProto toDexProto( + List<TypeReference> formalTypes, TypeReference returnType, DexItemFactory dexItemFactory) { + return toDexProto( + formalTypes, + returnType, + dexItemFactory, + classReference -> ClassReferenceUtils.toDexType(classReference, dexItemFactory)); + } + /** * Converts the given {@param formalTypes} and {@param returnType} to a {@link DexProto}. * @@ -55,6 +64,13 @@ formalType -> toDexType(formalType, dexItemFactory, classReferenceConverter))); } + public static DexType toDexType(TypeReference typeReference, DexItemFactory dexItemFactory) { + return toDexType( + typeReference, + dexItemFactory, + classReference -> ClassReferenceUtils.toDexType(classReference, dexItemFactory)); + } + /** * Converts the given {@param typeReference} to a {@link DexType}. *
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java index 6517f6e..a826739 100644 --- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java +++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -58,15 +58,20 @@ } public static Matcher<Diagnostic> diagnosticOrigin(Origin origin) { + return diagnosticOrigin(CoreMatchers.is(origin)); + } + + public static Matcher<Diagnostic> diagnosticOrigin(Matcher<Origin> originMatcher) { return new DiagnosticsMatcher() { @Override protected boolean eval(Diagnostic diagnostic) { - return diagnostic.getOrigin().equals(origin); + return originMatcher.matches(diagnostic.getOrigin()); } @Override protected void explain(Description description) { - description.appendText("origin ").appendText(origin.toString()); + description.appendText("origin with "); + originMatcher.describeTo(description); } }; }
diff --git a/src/test/java/com/android/tools/r8/OriginMatcher.java b/src/test/java/com/android/tools/r8/OriginMatcher.java new file mode 100644 index 0000000..8484ea3 --- /dev/null +++ b/src/test/java/com/android/tools/r8/OriginMatcher.java
@@ -0,0 +1,33 @@ +// Copyright (c) 2020, 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; + +import com.android.tools.r8.origin.Origin; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public abstract class OriginMatcher extends TypeSafeMatcher<Origin> { + + public static Matcher<Origin> hasParent(Origin parent) { + return new OriginMatcher() { + @Override + protected boolean matchesSafely(Origin origin) { + Origin current = origin; + do { + if (current == parent) { + return true; + } + current = current.parent(); + } while (current != null); + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("not a parent " + parent); + } + }; + } +}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java index e75ab6c..f93398c 100644 --- a/src/test/java/com/android/tools/r8/TestBase.java +++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -5,6 +5,7 @@ package com.android.tools.r8; import static com.android.tools.r8.TestBuilder.getTestingAnnotations; +import static com.android.tools.r8.ToolHelper.R8_TEST_BUCKET; import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION; import static com.google.common.collect.Lists.cartesianProduct; import static org.hamcrest.CoreMatchers.containsString; @@ -1865,4 +1866,50 @@ compileResult.assertAllInfoMessagesMatch( containsString("The generic super type is not the same as the class super type")); } + + public static boolean uploadJarsToCloudStorageIfTestFails( + ThrowingBiFunction<Path, Path, Boolean, Exception> test, Path expected, Path actual) + throws Exception { + boolean filesAreEqual = false; + Throwable error = null; + try { + filesAreEqual = test.apply(expected, actual); + } catch (AssertionError | Exception assertionError) { + error = assertionError; + } + if (filesAreEqual) { + return true; + } + // Only upload files if we are running on the bots. + if (ToolHelper.isBot()) { + try { + System.out.println("DIFFERENCE IN JARS DETECTED"); + System.out.println( + "***********************************************************************"); + String expectedSha = ToolHelper.uploadFileToGoogleCloudStorage(R8_TEST_BUCKET, expected); + System.out.println("EXPECTED JAR SHA1: " + expectedSha); + System.out.println( + String.format( + "DOWNLOAD BY: `download_from_google_storage.py --bucket %s %s -o %s`", + R8_TEST_BUCKET, expectedSha, expected.getFileName())); + String actualSha = ToolHelper.uploadFileToGoogleCloudStorage(R8_TEST_BUCKET, actual); + System.out.println("ACTUAL JAR SHA1: " + actualSha); + System.out.println( + String.format( + "DOWNLOAD BY: `download_from_google_storage.py --bucket %s %s -o %s`", + R8_TEST_BUCKET, expectedSha, actual.getFileName())); + System.out.println( + "***********************************************************************"); + } catch (Throwable e) { + e.printStackTrace(); + } + } + if (error != null) { + if (error instanceof Exception) { + throw (Exception) error; + } + throw new RuntimeException(error); + } + return false; + } }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java index 1e1b55f..3702430 100644 --- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,6 +10,8 @@ import com.android.tools.r8.TestBase.Backend; import com.android.tools.r8.debug.DebugTestConfig; +import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; import com.android.tools.r8.testing.AndroidBuildVersion; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; @@ -18,6 +20,7 @@ import com.android.tools.r8.utils.ForwardingOutputStream; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ThrowingOutputStream; +import com.android.tools.r8.utils.codeinspector.ArgumentPropagatorCodeScannerResultInspector; import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector; import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; @@ -103,6 +106,23 @@ B builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app) throws CompilationFailedException; + public T addArgumentPropagatorCodeScannerResultInspector( + ThrowableConsumer<ArgumentPropagatorCodeScannerResultInspector> inspector) { + return addOptionsModification( + options -> + options.testing.argumentPropagatorEventConsumer = + options.testing.argumentPropagatorEventConsumer.andThen( + new ArgumentPropagatorEventConsumer() { + @Override + public void acceptCodeScannerResult( + MethodStateCollectionByReference methodStates) { + inspector.acceptWithRuntimeException( + new ArgumentPropagatorCodeScannerResultInspector( + options.dexItemFactory(), methodStates)); + } + })); + } + public T addOptionsModification(Consumer<InternalOptions> optionsConsumer) { if (optionsConsumer != null) { this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer);
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index a38852e..a457e50 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -124,6 +124,8 @@ public static final String JAVA_CLASSES_DIR = BUILD_DIR + "classes/java/"; public static final String JDK_11_TESTS_CLASSES_DIR = JAVA_CLASSES_DIR + "jdk11Tests/"; + public static final String R8_TEST_BUCKET = "r8-test-results"; + public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.2.jar"; public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.2.jar"; @@ -217,6 +219,11 @@ return (backend == Backend.CF && outputMode == OutputMode.ClassFile) || backend == Backend.DEX; } + public static boolean isBot() { + String swarming_bot_id = System.getenv("SWARMING_BOT_ID"); + return swarming_bot_id != null && !swarming_bot_id.isEmpty(); + } + public static StringConsumer consumeString(Consumer<String> consumer) { return new StringConsumer() { @@ -2262,4 +2269,31 @@ return walker.filter(path -> path.toString().endsWith(endsWith)).collect(Collectors.toList()); } } + + /** This code only works if run with depot_tools on the path */ + public static String uploadFileToGoogleCloudStorage(String bucket, Path file) throws IOException { + ImmutableList.Builder<String> command = + new ImmutableList.Builder<String>() + .add("upload_to_google_storage.py") + .add("-f") + .add("--bucket") + .add(bucket) + .add(file.toAbsolutePath().toString()); + ProcessResult result = ToolHelper.runProcess(new ProcessBuilder(command.build())); + if (result.exitCode != 0) { + throw new RuntimeException( + "Could not upload " + + file + + " to cloud storage:\n" + + result.stdout + + "\n" + + result.stderr); + } + // Upload will add a sha1 file at the same location. + Path sha1file = file.resolveSibling(file.getFileName() + ".sha1"); + assert Files.exists(sha1file) : sha1file.toString(); + List<String> strings = Files.readAllLines(sha1file); + assert !strings.isEmpty(); + return strings.get(0); + } }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassTest.java new file mode 100644 index 0000000..1ccb626 --- /dev/null +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassTest.java
@@ -0,0 +1,108 @@ +// 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.apimodel; + +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; +import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; +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.utils.AndroidApiLevel; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeMatchers; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.lang.reflect.Method; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ApiModelInlineInSameClassTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public ApiModelInlineInSameClassTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testR8() throws Exception { + Method apiMethod = Api.class.getDeclaredMethod("apiLevel22"); + Method callingApi = ApiCaller.class.getDeclaredMethod("callingApi"); + Method notCallingApi = ApiCaller.class.getDeclaredMethod("notCallingApi"); + Method main = Main.class.getDeclaredMethod("main", String[].class); + testForR8(parameters.getBackend()) + .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, Main.class) + .addLibraryClasses(Api.class) + .addDefaultRuntimeLibrary(parameters) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1)) + .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .compile() + .inspect(verifyThat(parameters, notCallingApi).inlinedIntoFromApiLevel(main, L_MR1)) + .inspect( + inspector -> { + // No matter the api level, we should always inline callingApi into notCallingApi. + assertThat(inspector.method(callingApi), not(isPresent())); + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) { + ClassSubject mainSubject = inspector.clazz(Main.class); + MethodSubject mainMethodSubject = mainSubject.uniqueMethodWithName("main"); + assertThat(mainMethodSubject, isPresent()); + assertThat(mainMethodSubject, CodeMatchers.invokesMethodWithName("apiLevel22")); + } + }) + .addRunClasspathClasses(Api.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Api::apiLevel22"); + } + + public static class Api { + + public static void apiLevel22() { + System.out.println("Api::apiLevel22"); + } + } + + public static class ApiCaller { + + public static void callingApi() { + Api.apiLevel22(); + } + + public static void notCallingApi() { + // If api level information is not propagated correctly, inlining `callingApi` into + // `notCallingApi` will have an api call that is not modeled correctly and it could be inlined + // into its callers incorrectly. + callingApi(); + } + } + + public static class ApiCallerCaller { + + public static void foo() { + ApiCaller.notCallingApi(); + } + } + + public static class Main { + + public static void main(String[] args) { + ApiCallerCaller.foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/InvokeClinitTest.java b/src/test/java/com/android/tools/r8/cf/InvokeClinitTest.java new file mode 100644 index 0000000..568fff6 --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/InvokeClinitTest.java
@@ -0,0 +1,104 @@ +// 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.cf; + +import static org.hamcrest.CoreMatchers.anyOf; +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.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InvokeClinitTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withAllRuntimes() + .withApiLevel(AndroidApiLevel.B) + .enableApiLevelsForCf() + .build(); + } + + public InvokeClinitTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClasses(A.class) + .addProgramClassFileData(transformMain()) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatMatches( + anyOf( + containsString(ClassFormatError.class.getSimpleName()), + containsString(VerifyError.class.getSimpleName()))); + } + + @Test + public void testD8() { + assertThrows( + CompilationFailedException.class, + () -> + testForD8(parameters.getBackend()) + .addProgramClasses(A.class) + .addProgramClassFileData(transformMain()) + .setMinApi(parameters.getApiLevel()) + .compile()); + } + + @Test + public void testR8() { + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClasses(A.class) + .addProgramClassFileData(transformMain()) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .compile()); + } + + private byte[] transformMain() throws IOException { + return transformer(Main.class) + .transformMethodInsnInMethod( + "main", + (opcode, owner, name, descriptor, isInterface, continuation) -> + continuation.visitMethodInsn(opcode, owner, "<clinit>", descriptor, isInterface)) + .transform(); + } + + static class A { + static { + System.out.println("A.<clinit>"); + } + + static void willBeClinit() { + System.out.println("unused"); + } + } + + public static class Main { + + public static void main(String[] args) { + A.willBeClinit(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java index a8d57c0..e41bf8d 100644 --- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java +++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -14,7 +14,6 @@ import static org.junit.Assert.assertNotEquals; import com.android.tools.r8.ArchiveClassFileProvider; -import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.ExternalR8TestCompileResult; import com.android.tools.r8.TestBase; @@ -160,7 +159,7 @@ } @Test - public void testR8LibCompatibility() throws IOException, CompilationFailedException { + public void testR8LibCompatibility() throws Exception { // Produce r81 = R8Lib(R8WithDeps) and r82 = R8LibNoDeps + Deps(R8WithDeps) and test that r81 is // equal to r82. This test should only run if we are testing r8lib and we expect both R8libs to // be built by gradle. If we are not testing with R8Lib, do not run this test. @@ -184,7 +183,8 @@ .setMode(CompilationMode.RELEASE) .compile() .outputJar(); - assert filesAreEqual(runR81, runR82); + assert uploadJarsToCloudStorageIfTestFails( + BootstrapCurrentEqualityTest::filesAreEqual, runR81, runR82); } @Test @@ -243,12 +243,13 @@ assertEquals(result.getStdout(), runR8R8.getStdout()); assertEquals(result.getStderr(), runR8R8.getStderr()); // Check that the output jars are the same. - assertProgramsEqual(result.outputJar(), runR8R8.outputJar()); + uploadJarsToCloudStorageIfTestFails( + BootstrapCurrentEqualityTest::assertProgramsEqual, result.outputJar(), runR8R8.outputJar()); } - public static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception { + public static boolean assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception { if (filesAreEqual(expectedJar, actualJar)) { - return; + return true; } ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar); ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar); @@ -259,6 +260,7 @@ getClassAsBytes(expected, descriptor), getClassAsBytes(actual, descriptor)); } + return false; } public static boolean filesAreEqual(Path file1, Path file2) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java new file mode 100644 index 0000000..b0d261e --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
@@ -0,0 +1,126 @@ +// 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.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static com.android.tools.r8.OriginMatcher.hasParent; +import static org.hamcrest.CoreMatchers.allOf; +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.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.origin.Origin; +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.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 BasicConstantDynamicTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); + } + + private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true"); + + @Test + public void testReference() throws Exception { + assumeTrue(parameters.isCfRuntime()); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + testForJvm() + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), A.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.getRuntime().isDex()); + + assertThrows( + CompilationFailedException.class, + () -> + testForD8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + @Test + public void testR8() throws Exception { + assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(A.class) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + private byte[] getTransformedClasses() throws IOException { + return transformer(A.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", A.class, "myConstant", "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy2", A.class, "myConstant", "constantName", Object.class) + .transform(); + } + + public static class A { + + public static Object f() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object g() { + return "condy2"; // Will be transformed to Constant_DYNAMIC. + } + + public static void main(String[] args) { + System.out.println(f() != null); + System.out.println(f() == g()); + } + + 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/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java index ee5142f..d1a4d35 100644 --- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -5,6 +5,7 @@ import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static com.android.tools.r8.OriginMatcher.hasParent; import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR; import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; @@ -23,7 +24,7 @@ import com.android.tools.r8.ToolHelper.DexVm; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.cf.CfVersion; -import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.ArchiveResourceProvider; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.ZipUtils; @@ -141,28 +142,23 @@ } else { assertThrows( CompilationFailedException.class, - () -> - testForD8(parameters.getBackend()) - .addProgramFiles(testClasses.getInstrumented()) - .addProgramFiles(ToolHelper.JACOCO_AGENT) - .setMinApi(parameters.getApiLevel()) - .compileWithExpectedDiagnostics( - diagnostics -> { - // Check that the error is reported as an error to the diagnostics handler. - diagnostics.assertErrorsCount(1); - diagnostics.assertAllErrorsMatch( - allOf( - diagnosticMessage(containsString("Unsupported dynamic constant")), - // The fatal error is not given an origin, so it can't provide it. - // Note: This could be fixed by delaying reporting and associate the - // info - // at the top-level handler. It would require mangling of the - // diagnostic, - // so maybe not that elegant. - diagnosticOrigin(Origin.unknown()))); - diagnostics.assertWarningsCount(0); - diagnostics.assertInfosCount(0); - })); + () -> { + ArchiveResourceProvider provider = + ArchiveResourceProvider.fromArchive(testClasses.getInstrumented(), true); + testForD8(parameters.getBackend()) + .addProgramResourceProviders(provider) + .addProgramFiles(ToolHelper.JACOCO_AGENT) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + // Check that the error is reported as an error to the diagnostics handler. + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(provider.getOrigin())))); + }); + }); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java new file mode 100644 index 0000000..69cd883 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
@@ -0,0 +1,144 @@ +// 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.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static com.android.tools.r8.OriginMatcher.hasParent; +import static org.hamcrest.CoreMatchers.allOf; +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.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.origin.Origin; +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.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 MultipleNamedConstantDynamicTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); + } + + private static final String EXPECTED_OUTPUT = + StringUtils.lines("true", "true", "true", "true", "true"); + + @Test + public void testReference() throws Exception { + assumeTrue(parameters.isCfRuntime()); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + testForJvm() + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), A.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + + assertThrows( + CompilationFailedException.class, + () -> + testForD8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + @Test + public void testR8() throws Exception { + assumeTrue( + parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(A.class) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + private byte[] getTransformedClasses() throws IOException { + return transformer(A.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", A.class, "myConstant", "constantF", Object.class) + .transformConstStringToConstantDynamic( + "condy2", A.class, "myConstant", "constantF", Object.class) + .transformConstStringToConstantDynamic( + "condy3", A.class, "myConstant", "constantG", Object.class) + .transformConstStringToConstantDynamic( + "condy4", A.class, "myConstant", "constantG", Object.class) + .transform(); + } + + public static class A { + + public static Object f1() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object f2() { + return "condy2"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object g1() { + return "condy3"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object g2() { + return "condy4"; // Will be transformed to Constant_DYNAMIC. + } + + public static void main(String[] args) { + System.out.println(f1() != null); + System.out.println(f1() == f2()); + System.out.println(g1() != null); + System.out.println(g1() == g2()); + System.out.println(f1() != g2()); + } + + 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/MultipleTypesConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java new file mode 100644 index 0000000..b04792f --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
@@ -0,0 +1,147 @@ +// 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.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static com.android.tools.r8.OriginMatcher.hasParent; +import static org.hamcrest.CoreMatchers.allOf; +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.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.origin.Origin; +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.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 MultipleTypesConstantDynamicTest extends TestBase { + + @Parameter public TestParameters parameters; + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); + } + + private static final String EXPECTED_OUTPUT = + StringUtils.lines("true", "true", "true", "true", "true"); + + @Test + public void testReference() throws Exception { + assumeTrue(parameters.isCfRuntime()); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + testForJvm() + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), A.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + + assertThrows( + CompilationFailedException.class, + () -> + testForD8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + @Test + public void testR8() throws Exception { + assumeTrue( + parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(A.class) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + private byte[] getTransformedClasses() throws IOException { + return transformer(A.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", A.class, "myConstant", "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy2", A.class, "myConstant", "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy3", A.class, "myConstant", "constantName", boolean[].class) + .transformConstStringToConstantDynamic( + "condy4", A.class, "myConstant", "constantName", boolean[].class) + .transform(); + } + + public static class A { + + public static Object f1() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object f2() { + return "condy2"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object g1() { + return "condy3"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object g2() { + return "condy4"; // Will be transformed to Constant_DYNAMIC. + } + + public static void main(String[] args) { + System.out.println(f1() != null); + System.out.println(f1() == f2()); + System.out.println(g1() != null); + System.out.println(g1() == g2()); + System.out.println(f1() != g2()); + } + + private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) { + if (type.isArray()) { + return new boolean[0]; + } else { + return new Object(); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java new file mode 100644 index 0000000..24c9ed8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
@@ -0,0 +1,152 @@ +// 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.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static com.android.tools.r8.OriginMatcher.hasParent; +import static org.hamcrest.CoreMatchers.allOf; +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.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.Collection; +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 SharedBootstrapMethodConstantDynamicTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); + } + + private static final String EXPECTED_OUTPUT = + StringUtils.lines("true", "true", "true", "true", "true"); + + @Test + public void testReference() throws Exception { + assumeTrue(parameters.isCfRuntime()); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + testForJvm() + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), A.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + + assertThrows( + CompilationFailedException.class, + () -> + testForD8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + @Test + public void testR8() throws Exception { + assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(A.class) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("Unsupported dynamic constant")), + diagnosticOrigin(hasParent(Origin.unknown())))); + })); + } + + private Collection<byte[]> getTransformedClasses() throws IOException { + return ImmutableList.of( + transformer(A.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", A.class, "myConstant", "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy2", A.class, "myConstant", "constantName", Object.class) + .transform(), + transformer(B.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy3", A.class, "myConstant", "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy4", A.class, "myConstant", "constantName", Object.class) + .transform()); + } + + public static class A { + + public static Object f() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object g() { + return "condy2"; // Will be transformed to Constant_DYNAMIC. + } + + public static void main(String[] args) { + System.out.println(A.f() != null); + System.out.println(A.f() == A.g()); + System.out.println(B.f() != null); + System.out.println(B.f() == B.g()); + System.out.println(A.f() != B.g()); + } + + public static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) { + return new Object(); + } + } + + public static class B { + + public static Object f() { + return "condy3"; // Will be transformed to Constant_DYNAMIC. + } + + public static Object g() { + return "condy4"; // Will be transformed to Constant_DYNAMIC. + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java index 1eac80e..66bdd07 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java +++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.desugar.nestaccesscontrol; +import static com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest.uploadJarsToCloudStorageIfTestFails; import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; @@ -128,7 +129,8 @@ } prevRunResult = runResult; if (prevGeneratedJar != null) { - BootstrapCurrentEqualityTest.assertProgramsEqual(prevGeneratedJar, generatedJar); + uploadJarsToCloudStorageIfTestFails( + BootstrapCurrentEqualityTest::assertProgramsEqual, prevGeneratedJar, generatedJar); } prevGeneratedJar = generatedJar; } @@ -149,7 +151,8 @@ .compile() .outputJar(); if (prevGeneratedJar != null) { - BootstrapCurrentEqualityTest.assertProgramsEqual(prevGeneratedJar, generatedJar); + uploadJarsToCloudStorageIfTestFails( + BootstrapCurrentEqualityTest::assertProgramsEqual, prevGeneratedJar, generatedJar); } prevGeneratedJar = generatedJar; }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java index 3f0992a..389744e 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.utils.StringUtils; import java.nio.file.Path; import java.util.List; -import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -63,23 +62,35 @@ } @Test - public void testR8Cf() throws Exception { - Assume.assumeTrue(parameters.isCfRuntime()); - Path output = - testForR8(parameters.getBackend()) - .addProgramClassFileData(PROGRAM_DATA) - .setMinApi(parameters.getApiLevel()) - .addKeepRules(RECORD_KEEP_RULE) - .addKeepMainRule(MAIN_TYPE) - .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) - .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) - .compile() - .writeToZip(); - RecordTestUtils.assertRecordsAreRecords(output); - testForJvm() - .addRunClasspathFiles(output) - .enablePreview() + public void testR8() throws Exception { + if (parameters.isCfRuntime()) { + Path output = + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() + .writeToZip(); + RecordTestUtils.assertRecordsAreRecords(output); + testForJvm() + .addRunClasspathFiles(output) + .enablePreview() + .run(parameters.getRuntime(), MAIN_TYPE) + .assertSuccessWithOutput(EXPECTED_RESULT); + return; + } + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() .run(parameters.getRuntime(), MAIN_TYPE) .assertSuccessWithOutput(EXPECTED_RESULT); }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java index cbc7ef2..895aa97 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java +++ b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
@@ -8,31 +8,14 @@ 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.code.CfFrame; -import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.cf.code.CfInstruction; -import com.android.tools.r8.cf.code.CfInvoke; -import com.android.tools.r8.cf.code.CfTypeInstruction; import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase; -import com.android.tools.r8.desugar.records.RecordMethods.RecordStub; -import com.android.tools.r8.graph.CfCode; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.ir.desugar.records.RecordRewriter; -import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.FileUtils; import com.google.common.collect.ImmutableList; -import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; import java.nio.charset.StandardCharsets; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Comparator; -import java.util.Deque; import java.util.List; -import java.util.SortedMap; -import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -42,8 +25,6 @@ public class GenerateRecordMethods extends MethodGenerationBase { private final DexType GENERATED_TYPE = factory.createType("Lcom/android/tools/r8/ir/desugar/records/RecordCfMethods;"); - private final DexType RECORD_STUB_TYPE = - factory.createType(DescriptorUtils.javaTypeToDescriptor(RecordStub.class.getTypeName())); private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of(RecordMethods.class); protected final TestParameters parameters; @@ -72,77 +53,6 @@ return 2021; } - @Override - protected CfCode getCode(String holderName, String methodName, CfCode code) { - code.setInstructions( - code.getInstructions().stream() - .map(instruction -> rewriteRecordStub(instruction)) - .collect(Collectors.toList())); - return code; - } - - private CfInstruction rewriteRecordStub(CfInstruction instruction) { - if (instruction.isTypeInstruction()) { - CfTypeInstruction typeInstruction = instruction.asTypeInstruction(); - return typeInstruction.withType(rewriteType(typeInstruction.getType())); - } - if (instruction.isInvoke()) { - CfInvoke cfInvoke = instruction.asInvoke(); - DexMethod method = cfInvoke.getMethod(); - DexMethod newMethod = - factory.createMethod(rewriteType(method.holder), method.proto, rewriteName(method.name)); - return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface()); - } - if (instruction.isFrame()) { - CfFrame cfFrame = instruction.asFrame(); - return new CfFrame( - rewriteLocals(cfFrame.getLocalsAsSortedMap()), rewriteStack(cfFrame.getStack())); - } - return instruction; - } - - private String rewriteName(DexString name) { - return name.toString().equals("getFieldsAsObjects") - ? RecordRewriter.GET_FIELDS_AS_OBJECTS_METHOD_NAME - : name.toString(); - } - - private DexType rewriteType(DexType type) { - DexType baseType = type.isArrayType() ? type.toBaseType(factory) : type; - if (baseType != RECORD_STUB_TYPE) { - return type; - } - return type.isArrayType() - ? type.replaceBaseType(factory.recordType, factory) - : factory.recordType; - } - - private FrameType rewriteFrameType(FrameType frameType) { - if (frameType.isInitialized() && frameType.getInitializedType().isReferenceType()) { - DexType newType = rewriteType(frameType.getInitializedType()); - if (newType == frameType.getInitializedType()) { - return frameType; - } - return FrameType.initialized(newType); - } else { - assert !frameType.isUninitializedNew(); - assert !frameType.isUninitializedThis(); - return frameType; - } - } - - private SortedMap<Integer, FrameType> rewriteLocals(SortedMap<Integer, FrameType> locals) { - Int2ReferenceSortedMap<FrameType> newLocals = new Int2ReferenceAVLTreeMap<>(); - locals.forEach((index, local) -> newLocals.put((int) index, rewriteFrameType(local))); - return newLocals; - } - - private Deque<FrameType> rewriteStack(Deque<FrameType> stack) { - ArrayDeque<FrameType> newStack = new ArrayDeque<>(); - stack.forEach(frameType -> newStack.add(rewriteFrameType(frameType))); - return newStack; - } - @Test public void testRecordMethodsGenerated() throws Exception { ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java index c6b3f68..08b0e85 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.utils.StringUtils; import java.nio.file.Path; import java.util.List; -import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -63,23 +62,35 @@ } @Test - public void testR8Cf() throws Exception { - Assume.assumeTrue(parameters.isCfRuntime()); - Path output = - testForR8(parameters.getBackend()) - .addProgramClassFileData(PROGRAM_DATA) - .setMinApi(parameters.getApiLevel()) - .addKeepRules(RECORD_KEEP_RULE) - .addKeepMainRule(MAIN_TYPE) - .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) - .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) - .compile() - .writeToZip(); - RecordTestUtils.assertRecordsAreRecords(output); - testForJvm() - .addRunClasspathFiles(output) - .enablePreview() + public void testR8() throws Exception { + if (parameters.isCfRuntime()) { + Path output = + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() + .writeToZip(); + RecordTestUtils.assertRecordsAreRecords(output); + testForJvm() + .addRunClasspathFiles(output) + .enablePreview() + .run(parameters.getRuntime(), MAIN_TYPE) + .assertSuccessWithOutput(EXPECTED_RESULT); + return; + } + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() .run(parameters.getRuntime(), MAIN_TYPE) .assertSuccessWithOutput(EXPECTED_RESULT); }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java index 55cb175..9bf3327 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.utils.StringUtils; import java.nio.file.Path; import java.util.List; -import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -76,23 +75,35 @@ } @Test - public void testR8Cf() throws Exception { - Assume.assumeTrue(parameters.isCfRuntime()); - Path output = - testForR8(parameters.getBackend()) - .addProgramClassFileData(PROGRAM_DATA) - .setMinApi(parameters.getApiLevel()) - .addKeepRules(RECORD_KEEP_RULE) - .addKeepMainRule(MAIN_TYPE) - .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) - .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) - .compile() - .writeToZip(); - RecordTestUtils.assertRecordsAreRecords(output); - testForJvm() - .addRunClasspathFiles(output) - .enablePreview() + public void testR8() throws Exception { + if (parameters.isCfRuntime()) { + Path output = + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() + .writeToZip(); + RecordTestUtils.assertRecordsAreRecords(output); + testForJvm() + .addRunClasspathFiles(output) + .enablePreview() + .run(parameters.getRuntime(), MAIN_TYPE) + .assertSuccessWithOutput(EXPECTED_RESULT); + return; + } + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() .run(parameters.getRuntime(), MAIN_TYPE) .assertSuccessWithOutput(EXPECTED_RESULT); }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java index 7b1b2e5..a1fc48e 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
@@ -10,14 +10,14 @@ // rewrites relevant calls to one of the following methods. public class RecordMethods { - public static String toString(RecordStub recordInstance, String simpleName, String fieldNames) { + public static String toString( + Object[] recordFieldsAsObjects, String simpleName, String fieldNames) { // Example: "Person[name=Jane Doe, age=42]" String[] fieldNamesSplit = fieldNames.isEmpty() ? new String[0] : fieldNames.split(";"); - Object[] fields = recordInstance.getFieldsAsObjects(); StringBuilder builder = new StringBuilder(); builder.append(simpleName).append("["); for (int i = 0; i < fieldNamesSplit.length; i++) { - builder.append(fieldNamesSplit[i]).append("=").append(fields[i]); + builder.append(fieldNamesSplit[i]).append("=").append(recordFieldsAsObjects[i]); if (i != fieldNamesSplit.length - 1) { builder.append(", "); } @@ -26,18 +26,7 @@ return builder.toString(); } - public static int hashCode(RecordStub recordInstance) { - return 31 * Arrays.hashCode(recordInstance.getFieldsAsObjects()) - + recordInstance.getClass().hashCode(); - } - - public static boolean equals(RecordStub recordInstance, Object other) { - return recordInstance.getClass() == other.getClass() - && Arrays.equals( - ((RecordStub) other).getFieldsAsObjects(), recordInstance.getFieldsAsObjects()); - } - - public abstract static class RecordStub { - abstract Object[] getFieldsAsObjects(); + public static int hashCode(Class<?> recordClass, Object[] recordFieldsAsObjects) { + return 31 * Arrays.hashCode(recordFieldsAsObjects) + recordClass.hashCode(); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java index c36375e..36b2377 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.utils.StringUtils; import java.nio.file.Path; import java.util.List; -import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -65,23 +64,35 @@ } @Test - public void testR8Cf() throws Exception { - Assume.assumeTrue(parameters.isCfRuntime()); - Path output = - testForR8(parameters.getBackend()) - .addProgramClassFileData(PROGRAM_DATA) - .setMinApi(parameters.getApiLevel()) - .addKeepRules(RECORD_KEEP_RULE) - .addKeepMainRule(MAIN_TYPE) - .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) - .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) - .compile() - .writeToZip(); - RecordTestUtils.assertRecordsAreRecords(output); - testForJvm() - .addRunClasspathFiles(output) - .enablePreview() + public void testR8() throws Exception { + if (parameters.isCfRuntime()) { + Path output = + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() + .writeToZip(); + RecordTestUtils.assertRecordsAreRecords(output); + testForJvm() + .addRunClasspathFiles(output) + .enablePreview() + .run(parameters.getRuntime(), MAIN_TYPE) + .assertSuccessWithOutput(EXPECTED_RESULT); + return; + } + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() .run(parameters.getRuntime(), MAIN_TYPE) .assertSuccessWithOutput(EXPECTED_RESULT); }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java index 51596a1..08c9468 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.utils.StringUtils; import java.nio.file.Path; import java.util.List; -import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -64,23 +63,35 @@ } @Test - public void testR8Cf() throws Exception { - Assume.assumeTrue(parameters.isCfRuntime()); - Path output = - testForR8(parameters.getBackend()) - .addProgramClassFileData(PROGRAM_DATA) - .setMinApi(parameters.getApiLevel()) - .addKeepRules(RECORD_KEEP_RULE) - .addKeepMainRule(MAIN_TYPE) - .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) - .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) - .compile() - .writeToZip(); - RecordTestUtils.assertRecordsAreRecords(output); - testForJvm() - .addRunClasspathFiles(output) - .enablePreview() + public void testR8() throws Exception { + if (parameters.isCfRuntime()) { + Path output = + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() + .writeToZip(); + RecordTestUtils.assertRecordsAreRecords(output); + testForJvm() + .addRunClasspathFiles(output) + .enablePreview() + .run(parameters.getRuntime(), MAIN_TYPE) + .assertSuccessWithOutput(EXPECTED_RESULT); + return; + } + testForR8(parameters.getBackend()) + .addProgramClassFileData(PROGRAM_DATA) + .setMinApi(parameters.getApiLevel()) + .addKeepRules(RECORD_KEEP_RULE) + .addKeepMainRule(MAIN_TYPE) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() .run(parameters.getRuntime(), MAIN_TYPE) .assertSuccessWithOutput(EXPECTED_RESULT); }
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java new file mode 100644 index 0000000..1e2140c --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java
@@ -0,0 +1,130 @@ +// 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.optimize.argumentpropagation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + +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.HorizontallyMergedClassesInspector; +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 ArgumentPropagationToClassesOutsideLowerBoundTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject aMethodSubject = inspector.clazz(A.class).uniqueMethodWithName("m"); + assertThat(aMethodSubject, isPresent()); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Not null"))); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Null"))); + + // TODO(b/190154391): B.m() is always called with non-null. + MethodSubject bMethodSubject = inspector.clazz(B.class).uniqueMethodWithName("m"); + assertThat(bMethodSubject, isPresent()); + assertTrue( + bMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B: Not null"))); + assertTrue( + bMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B: Null"))); + + // TODO(b/190154391): C.m() is always called with null. + MethodSubject cMethodSubject = inspector.clazz(C.class).uniqueMethodWithName("m"); + assertThat(cMethodSubject, isPresent()); + assertTrue( + cMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("C: Not null"))); + assertTrue( + cMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("C: Null"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A: Not null", "A: Null"); + } + + static class Main { + + public static void main(String[] args) { + A aOrB = System.currentTimeMillis() > 0 ? new A() : new B(); + aOrB.m(new Object()); + + A aOrC = System.currentTimeMillis() > 0 ? new A() : new C(); + aOrC.m(null); + } + } + + static class A { + + void m(Object o) { + if (o != null) { + System.out.println("A: Not null"); + } else { + System.out.println("A: Null"); + } + } + } + + @NoHorizontalClassMerging + static class B extends A { + + void m(Object o) { + if (o != null) { + System.out.println("B: Not null"); + } else { + System.out.println("B: Null"); + } + } + } + + @NoHorizontalClassMerging + static class C extends A { + + void m(Object o) { + if (o != null) { + System.out.println("C: Not null"); + } else { + System.out.println("C: Null"); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java new file mode 100644 index 0000000..36c74a1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java
@@ -0,0 +1,85 @@ +// 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.optimize.argumentpropagation; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.BooleanBox; +import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; +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 ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + BooleanBox inspected = new BooleanBox(); + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addArgumentPropagatorCodeScannerResultInspector( + inspector -> + inspector + .assertHasUnknownMethodState( + Reference.methodFromMethod(A.class.getDeclaredMethod("test"))) + .assertHasBottomMethodState( + Reference.methodFromMethod(B.class.getDeclaredMethod("test"))) + .apply(ignore -> inspected.set())) + .addOptionsModification( + options -> + options + .callSiteOptimizationOptions() + .setEnableExperimentalArgumentPropagation(true)) + .addVerticallyMergedClassesInspector( + VerticallyMergedClassesInspector::assertNoClassesMerged) + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A"); + assertTrue(inspected.isTrue()); + } + + static class Main { + + public static void main(String[] args) { + A aOrB = System.currentTimeMillis() >= 0 ? new A() : new B(); + aOrB.test(); + } + } + + @NoVerticalClassMerging + static class A { + + void test() { + System.out.println("A"); + } + } + + static class B extends A { + + @Override + void test() { + System.out.println("B"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicVirtualMethodTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicVirtualMethodTest.java new file mode 100644 index 0000000..74ba4e2 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicVirtualMethodTest.java
@@ -0,0 +1,74 @@ +// 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.optimize.argumentpropagation; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NeverClassInline; +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.references.Reference; +import com.android.tools.r8.utils.BooleanBox; +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 MonomorphicVirtualMethodTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + BooleanBox inspected = new BooleanBox(); + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addArgumentPropagatorCodeScannerResultInspector( + inspector -> + inspector + .assertHasMonomorphicMethodState( + Reference.methodFromMethod(A.class.getDeclaredMethod("m", int.class))) + .apply(ignore -> inspected.set())) + .addOptionsModification( + options -> + options + .callSiteOptimizationOptions() + .setEnableExperimentalArgumentPropagation(true)) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("42"); + assertTrue(inspected.isTrue()); + } + + static class Main { + + public static void main(String[] args) { + new A().m(42); + } + } + + @NeverClassInline + static class A { + + @NeverInline + void m(int x) { + System.out.println(x); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicVirtualMethodWithInterfaceMethodSiblingTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicVirtualMethodWithInterfaceMethodSiblingTest.java new file mode 100644 index 0000000..8731617 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicVirtualMethodWithInterfaceMethodSiblingTest.java
@@ -0,0 +1,115 @@ +// 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.optimize.argumentpropagation; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.BooleanBox; +import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; +import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; +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 MonomorphicVirtualMethodWithInterfaceMethodSiblingTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + BooleanBox inspected = new BooleanBox(); + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addArgumentPropagatorCodeScannerResultInspector( + inspector -> + inspector + .assertHasPolymorphicMethodState( + Reference.methodFromMethod(I.class.getDeclaredMethod("m", int.class))) + .assertHasMonomorphicMethodState( + Reference.methodFromMethod(A.class.getDeclaredMethod("m", int.class))) + .assertHasBottomMethodState( + Reference.methodFromMethod(C.class.getDeclaredMethod("m", int.class))) + .apply(ignore -> inspected.set())) + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .addOptionsModification( + options -> + options + .callSiteOptimizationOptions() + .setEnableExperimentalArgumentPropagation(true)) + .addVerticallyMergedClassesInspector( + VerticallyMergedClassesInspector::assertNoClassesMerged) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A: 42", "A: 42"); + assertTrue(inspected.isTrue()); + } + + static class Main { + + public static void main(String[] args) { + // Since A.m() does not override any methods (ignoring siblings) and do not have any overrides + // this invoke leads to a monomorphic method state for A.m(). + A a = new A(); + a.m(42); + + // This invoke leads to a polymorphic method state for I.m(). When we propagate this + // information to A.m() in the interface method propagation phase, we need to transform the + // polymorphic method state into a monomorphic method state. + I i = System.currentTimeMillis() >= 0 ? new B() : new C(); + i.m(42); + } + } + + interface I { + + void m(int x); + } + + @NeverClassInline + @NoHorizontalClassMerging + @NoVerticalClassMerging + static class A { + + @NeverInline + public void m(int x) { + System.out.println("A: " + x); + } + } + + static class B extends A implements I {} + + @NoHorizontalClassMerging + static class C implements I { + + @Override + public void m(int x) { + System.out.println("C: " + x); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java new file mode 100644 index 0000000..ade398c --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java
@@ -0,0 +1,79 @@ +// 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.optimize.argumentpropagation; + +import static org.junit.Assert.assertTrue; + +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.references.Reference; +import com.android.tools.r8.utils.BooleanBox; +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 ParameterWithUnknownArgumentInformationWidenedToUnknownTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + BooleanBox inspected = new BooleanBox(); + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addArgumentPropagatorCodeScannerResultInspector( + inspector -> + inspector + .assertHasUnknownMethodState( + Reference.methodFromMethod(Main.class.getDeclaredMethod("test", A.class))) + .apply(ignore -> inspected.set())) + .addOptionsModification( + options -> + options + .callSiteOptimizationOptions() + .setEnableExperimentalArgumentPropagation(true)) + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(null, "A"); + assertTrue(inspected.isTrue()); + } + + static class Main { + + public static void main(String[] args) { + A alwaysNull = null; + A neverNull = new A(); + test(alwaysNull); + test(neverNull); + } + + @NeverInline + static void test(A a) { + System.out.println(a); + } + } + + static class A { + + @Override + public String toString() { + return "A"; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java new file mode 100644 index 0000000..03d63d9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java
@@ -0,0 +1,147 @@ +// 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.optimize.argumentpropagation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + +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.HorizontallyMergedClassesInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; +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 UpwardsInterfacePropagationToUnrelatedMethodTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> + options + .callSiteOptimizationOptions() + .setEnableExperimentalArgumentPropagation(true)) + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .addVerticallyMergedClassesInspector( + VerticallyMergedClassesInspector::assertNoClassesMerged) + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject aMethodSubject = inspector.clazz(A.class).uniqueMethodWithName("m"); + assertThat(aMethodSubject, isPresent()); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Not null"))); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Null"))); + + MethodSubject b2MethodSubject = inspector.clazz(B2.class).uniqueMethodWithName("m"); + assertThat(b2MethodSubject, isPresent()); + assertTrue( + b2MethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B2: Not null"))); + assertTrue( + b2MethodSubject + .streamInstructions() + .noneMatch(instruction -> instruction.isConstString("B2: Null"))); + + MethodSubject b3MethodSubject = inspector.clazz(B3.class).uniqueMethodWithName("m"); + assertThat(b3MethodSubject, isPresent()); + assertTrue( + b3MethodSubject + .streamInstructions() + .noneMatch(instruction -> instruction.isConstString("B3: Not null"))); + assertTrue( + b3MethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B3: Null"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A: Not null", "A: Null"); + } + + static class Main { + + public static void main(String[] args) { + // Call A.m() or B2.m() with a non-null object. + A a = System.currentTimeMillis() >= 0 ? new B() : new B2(); + a.m(new Object()); + + // Call A.m() or B3.m() with a null object. + I i = System.currentTimeMillis() >= 0 ? new B() : new B3(); + i.m(null); + } + } + + interface I { + + void m(Object o); + } + + static class A { + + public void m(Object o) { + if (o != null) { + System.out.println("A: Not null"); + } else { + System.out.println("A: Null"); + } + } + } + + @NoHorizontalClassMerging + static class B extends A implements I {} + + @NoHorizontalClassMerging + static class B2 extends A { + + @Override + public void m(Object o) { + if (o != null) { + System.out.println("B2: Not null"); + } else { + System.out.println("B2: Null"); + } + } + } + + static class B3 implements I { + + @Override + public void m(Object o) { + if (o != null) { + System.out.println("B3: Not null"); + } else { + System.out.println("B3: Null"); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/VirtualMethodWithConstantArgumentThroughSiblingInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/VirtualMethodWithConstantArgumentThroughSiblingInterfaceMethodTest.java new file mode 100644 index 0000000..7be4823 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/VirtualMethodWithConstantArgumentThroughSiblingInterfaceMethodTest.java
@@ -0,0 +1,102 @@ +// 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.optimize.argumentpropagation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +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; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class VirtualMethodWithConstantArgumentThroughSiblingInterfaceMethodTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> + options + .callSiteOptimizationOptions() + .setEnableExperimentalArgumentPropagation(true)) + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + // The A.m() and C.m() methods have been optimized. + for (Class<?> clazz : new Class[] {A.class, C.class}) { + ClassSubject aClassSubject = inspector.clazz(clazz); + assertThat(aClassSubject, isPresent()); + MethodSubject aMethodSubject = aClassSubject.uniqueMethodWithName("m"); + assertThat(aMethodSubject, isPresent()); + assertTrue(aMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf)); + } + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A: Not null"); + } + + static class Main { + + public static void main(String[] args) { + I i = System.currentTimeMillis() > 0 ? new B() : new C(); + i.m(new Object()); + } + } + + @NoVerticalClassMerging + interface I { + + void m(Object o); + } + + @NoVerticalClassMerging + static class A { + + public void m(Object o) { + if (o != null) { + System.out.println("A: Not null"); + } else { + System.out.println("A: Null"); + } + } + } + + static class B extends A implements I {} + + static class C implements I { + + @Override + public void m(Object o) { + if (o != null) { + System.out.println("C: Not null"); + } else { + System.out.println("C: Null"); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageCloneTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageCloneTest.java new file mode 100644 index 0000000..722a9cc --- /dev/null +++ b/src/test/java/com/android/tools/r8/repackage/RepackageCloneTest.java
@@ -0,0 +1,85 @@ +// 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.repackage; + +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class RepackageCloneTest extends RepackageTestBase { + + public RepackageCloneTest( + String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) { + super(flattenPackageHierarchyOrRepackageClasses, parameters); + } + + @Test + public void testClone() throws Exception { + testForR8Compat(parameters.getBackend()) + .addProgramClasses(A.class, B.class, C.class, Main.class) + .addKeepMainRule(Main.class) + .addKeepClassRulesWithAllowObfuscation(A.class) + // Ensure we keep values() which has a call to clone. + .addKeepRules("-keepclassmembers class " + typeName(A.class) + " { *; }") + .apply(this::configureRepackaging) + .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .compile() + .inspect( + inspector -> { + assertThat(A.class, isRepackaged(inspector)); + assertThat(B.class, isRepackaged(inspector)); + assertThat(C.class, isRepackaged(inspector)); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("foo", "null"); + } + + public enum A { + foo; + } + + @NoHorizontalClassMerging + public static class B { + + @NeverInline + public static void foo() { + try { + new B().clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + } + } + + @NoHorizontalClassMerging + public static class C { + + @NeverInline + public static void foo() { + System.out.println(new A[10].clone()[1]); + ; + } + } + + public static class Main { + + public static void main(String[] args) { + System.out.println(A.foo); + B.foo(); + C.foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java deleted file mode 100644 index b15d446..0000000 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java +++ /dev/null
@@ -1,156 +0,0 @@ -// Copyright (c) 2020, 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.resolution.interfacetargets; - -import static org.hamcrest.core.StringContains.containsString; -import static org.junit.Assume.assumeTrue; - -import com.android.tools.r8.CompilationFailedException; -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.TestRuntime.DexRuntime; -import com.android.tools.r8.ToolHelper.DexVm; -import com.android.tools.r8.dex.Constants; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.transformers.ClassTransformer; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import org.hamcrest.CoreMatchers; -import org.hamcrest.Matcher; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.objectweb.asm.MethodVisitor; - -@RunWith(Parameterized.class) -public class InvokeInterfaceClInitTest extends TestBase { - - private final TestParameters parameters; - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withAllRuntimesAndApiLevels().build(); - } - - public InvokeInterfaceClInitTest(TestParameters parameters) { - this.parameters = parameters; - } - - @Test - public void testResolution() throws Exception { - assumeTrue(parameters.useRuntimeAsNoneRuntime()); - AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness( - buildClasses(A.class, B.class) - .addClassProgramData(transformI(), transformMain()) - .addLibraryFile(parameters.getDefaultRuntimeLibrary()) - .build(), - Main.class); - AppInfoWithLiveness appInfo = appView.appInfo(); - DexMethod method = buildNullaryVoidMethod(I.class, "<clinit>", appInfo.dexItemFactory()); - DexProgramClass context = - appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); - Assert.assertThrows( - AssertionError.class, - () -> - appInfo - .resolveMethodOnInterface(method) - .lookupVirtualDispatchTargets(context, appInfo)); - } - - private Matcher<String> getExpected() { - if (parameters.getRuntime().isCf()) { - Matcher<String> expected = containsString("java.lang.VerifyError"); - // JDK 9 and 11 output VerifyError or ClassFormatError non-deterministically. - if (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) { - expected = CoreMatchers.anyOf(expected, containsString("java.lang.ClassFormatError")); - } - return expected; - } - assert parameters.getRuntime().isDex(); - DexRuntime dexRuntime = parameters.getRuntime().asDex(); - if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) { - return containsString("NoSuchMethodError"); - } - return containsString("java.lang.VerifyError"); - } - - @Test - public void testRuntimeClInit() - throws IOException, CompilationFailedException, ExecutionException { - testForRuntime(parameters) - .addProgramClasses(A.class, B.class) - .addProgramClassFileData(transformMain(), transformI()) - .run(parameters.getRuntime(), Main.class) - .assertFailureWithErrorThatMatches(getExpected()); - } - - @Test - public void testR8ClInit() throws IOException, CompilationFailedException, ExecutionException { - testForR8(parameters.getBackend()) - .addProgramClasses(A.class, B.class) - .addProgramClassFileData(transformMain(), transformI()) - .addKeepMainRule(Main.class) - .setMinApi(parameters.getApiLevel()) - .run(parameters.getRuntime(), Main.class) - .assertFailureWithErrorThatMatches(getExpected()); - } - - private byte[] transformI() throws IOException { - return transformer(I.class) - .addClassTransformer( - new ClassTransformer() { - @Override - public MethodVisitor visitMethod( - int access, - String name, - String descriptor, - String signature, - String[] exceptions) { - return super.visitMethod( - access | Constants.ACC_STATIC, "<clinit>", descriptor, signature, exceptions); - } - }) - .transform(); - } - - private byte[] transformMain() throws IOException { - return transformer(Main.class) - .transformMethodInsnInMethod( - "callClInit", - (opcode, owner, name, descriptor, isInterface, continuation) -> - continuation.visitMethodInsn(opcode, owner, "<clinit>", descriptor, isInterface)) - .transform(); - } - - public interface I { - - default void foo() { // <-- will be rewritten to <clinit> - System.out.println("I.foo"); - } - } - - public static class A implements I {} - - public static class B implements I {} - - public static class Main { - - public static void main(String[] args) { - callClInit(args.length == 0 ? new A() : new B()); - } - - private static void callClInit(I i) { - i.foo(); // <-- will be i.<clinit>() - } - } -}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java index ea7b6cc..2bbcf4a 100644 --- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java +++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -38,10 +38,12 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; 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.Type; public class ClassFileTransformer { @@ -890,6 +892,37 @@ }); } + public ClassFileTransformer transformConstStringToConstantDynamic( + String constantName, + Class<?> bootstrapMethodHolder, + String bootstrapMethodName, + String name, + Class<?> type) { + return addMethodTransformer( + new MethodTransformer() { + @Override + public void visitLdcInsn(Object value) { + String bootstrapMethodSignature = + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"; + if (value instanceof String && value.equals(constantName)) { + super.visitLdcInsn( + new ConstantDynamic( + name, + Reference.classFromClass(type).getDescriptor(), + new Handle( + Opcodes.H_INVOKESTATIC, + DescriptorUtils.getClassBinaryName(bootstrapMethodHolder), + bootstrapMethodName, + bootstrapMethodSignature, + false), + new Object[] {})); + } else { + super.visitLdcInsn(value); + } + } + }); + } + @FunctionalInterface private interface VisitMethodInsnCallback { void visitMethodInsn(
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java new file mode 100644 index 0000000..7fe9502 --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java
@@ -0,0 +1,82 @@ +// 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.utils.codeinspector; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.ThrowableConsumer; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState; +import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.utils.MethodReferenceUtils; +import java.util.function.Predicate; + +public class ArgumentPropagatorCodeScannerResultInspector { + + private final DexItemFactory dexItemFactory; + private final MethodStateCollectionByReference methodStates; + + public ArgumentPropagatorCodeScannerResultInspector( + DexItemFactory dexItemFactory, MethodStateCollectionByReference methodStates) { + this.dexItemFactory = dexItemFactory; + this.methodStates = methodStates; + } + + public ArgumentPropagatorCodeScannerResultInspector apply( + ThrowableConsumer<ArgumentPropagatorCodeScannerResultInspector> consumer) { + consumer.acceptWithRuntimeException(this); + return this; + } + + public ArgumentPropagatorCodeScannerResultInspector assertHasBottomMethodState( + MethodReference methodReference) { + return assertHasMethodStateThatMatches( + "Expected method state for " + + MethodReferenceUtils.toSourceString(methodReference) + + " to be bottom", + methodReference, + MethodState::isBottom); + } + + public ArgumentPropagatorCodeScannerResultInspector assertHasMonomorphicMethodState( + MethodReference methodReference) { + return assertHasMethodStateThatMatches( + "Expected method state for " + + MethodReferenceUtils.toSourceString(methodReference) + + " to be monomorphic", + methodReference, + MethodState::isMonomorphic); + } + + public ArgumentPropagatorCodeScannerResultInspector assertHasPolymorphicMethodState( + MethodReference methodReference) { + return assertHasMethodStateThatMatches( + "Expected method state for " + + MethodReferenceUtils.toSourceString(methodReference) + + " to be polymorphic", + methodReference, + MethodState::isPolymorphic); + } + + public ArgumentPropagatorCodeScannerResultInspector assertHasUnknownMethodState( + MethodReference methodReference) { + return assertHasMethodStateThatMatches( + "Expected method state for " + + MethodReferenceUtils.toSourceString(methodReference) + + " to be unknown", + methodReference, + MethodState::isUnknown); + } + + public ArgumentPropagatorCodeScannerResultInspector assertHasMethodStateThatMatches( + String message, MethodReference methodReference, Predicate<MethodState> predicate) { + DexMethod method = MethodReferenceUtils.toDexMethod(methodReference, dexItemFactory); + MethodState methodState = methodStates.get(method); + assertTrue(message + ", was: " + methodState, predicate.test(methodState)); + return this; + } +}
diff --git a/tools/youtube_data.py b/tools/youtube_data.py index 64bc995..34426c0 100644 --- a/tools/youtube_data.py +++ b/tools/youtube_data.py
@@ -125,10 +125,10 @@ def GetMemoryData(version): assert version == '16.20' return { - 'find-xmx-min': 2800, - 'find-xmx-max': 3200, + 'find-xmx-min': 3150, + 'find-xmx-max': 3300, 'find-xmx-range': 64, - 'oom-threshold': 3000, + 'oom-threshold': 3100, # TODO(b/143431825): Youtube can OOM randomly in memory configurations # that should work. 'skip-find-xmx-max': True,