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,