Merge commit '9c538dbccb1c1252a36b6c430d47425d5ecdc625' into dev-release
diff --git a/.gitignore b/.gitignore
index d78f4a4..f16c1ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,8 +112,8 @@
third_party/kotlin/kotlin-compiler-1.3.72
third_party/kotlin/kotlin-compiler-1.4.20.tar.gz
third_party/kotlin/kotlin-compiler-1.4.20
-third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz
-third_party/kotlin/kotlin-compiler-1.5.0-M2
+third_party/kotlin/kotlin-compiler-1.5.0.tar.gz
+third_party/kotlin/kotlin-compiler-1.5.0
third_party/kotlinx-coroutines-1.3.6.tar.gz
third_party/kotlinx-coroutines-1.3.6
third_party/nest/*
diff --git a/build.gradle b/build.gradle
index 1dc777d..01d41fc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
ext {
androidSupportVersion = '25.4.0'
- asmVersion = '9.0' // When updating update tools/asmifier.py and Toolhelper as well.
+ asmVersion = '9.2' // When updating update tools/asmifier.py and Toolhelper as well.
espressoVersion = '3.0.0'
fastutilVersion = '7.2.0'
guavaVersion = '23.0'
@@ -45,7 +45,7 @@
mockitoVersion = '2.10.0'
// The kotlin version is only here to specify the kotlin language level,
// all kotlin compilations are done in tests.
- kotlinVersion = '1.3.72'
+ kotlinVersion = '1.5.0'
kotlinExtMetadataJVMVersion = '0.2.0'
smaliVersion = '2.2b4'
errorproneVersion = '2.3.2'
@@ -331,7 +331,7 @@
"kotlin/kotlin-compiler-1.3.41",
"kotlin/kotlin-compiler-1.3.72",
"kotlin/kotlin-compiler-1.4.20",
- "kotlin/kotlin-compiler-1.5.0-M2",
+ "kotlin/kotlin-compiler-1.5.0",
"kotlinx-coroutines-1.3.6",
"openjdk/openjdk-rt-1.8",
"openjdk/desugar_jdk_libs",
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java
index 3b502b5..1c0e170 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -123,8 +123,7 @@
private boolean hasDesugaredLibraryConfiguration() {
return desugaredLibraryConfiguration != null
- && desugaredLibraryConfiguration
- != DesugaredLibraryConfiguration.EMPTY_DESUGARED_LIBRARY_CONFIGURATION;
+ && !desugaredLibraryConfiguration.isEmptyConfiguration();
}
public String getDesugaredLibraryJsonSource() {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3bd0653..c2ee174 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -357,7 +357,7 @@
}
AnnotationRemover.Builder annotationRemoverBuilder =
- options.isShrinking() ? AnnotationRemover.builder() : null;
+ options.isShrinking() ? AnnotationRemover.builder(Mode.INITIAL_TREE_SHAKING) : null;
AppView<AppInfoWithLiveness> appViewWithLiveness =
runEnqueuer(
annotationRemoverBuilder,
@@ -640,6 +640,7 @@
assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
assert appView.validateUnboxedEnumsHaveBeenPruned();
+ assert appView.withLiveness().appInfo().verifyNoIteratingOverPrunedClasses();
processWhyAreYouKeepingAndCheckDiscarded(
appView.rootSet(),
@@ -653,12 +654,20 @@
executorService);
// Remove annotations that refer to types that no longer exist.
- AnnotationRemover.builder()
+ AnnotationRemover.builder(Mode.FINAL_TREE_SHAKING)
.build(appView.withLiveness(), removedClasses)
.run(executorService);
new GenericSignatureRewriter(
appView, NamingLens.getIdentityLens(), genericContextBuilder)
.run(appView.appInfo().classes(), executorService);
+ assert appView.checkForTesting(
+ () ->
+ GenericSignatureCorrectnessHelper.createForVerification(
+ appView,
+ GenericSignatureContextBuilder.create(appView.appInfo().classes()))
+ .run(appView.appInfo().classes())
+ .isValid())
+ : "Could not validate generic signatures";
// Synthesize fields for triggering class initializers.
new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService);
@@ -738,6 +747,9 @@
// need to build IR.
appView.dexItemFactory().clearTypeElementsCache();
+ GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging =
+ GenericSignatureContextBuilder.create(appView.appInfo().classes());
+
// Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
// are always merged.
HorizontalClassMerger.createForFinalClassMerging(appView)
@@ -812,6 +824,17 @@
options.syntheticProguardRulesConsumer.accept(synthesizedProguardRules);
}
+ NamingLens prefixRewritingNamingLens =
+ PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
+
+ timing.begin("MinifyKotlinMetadata");
+ new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForR8(executorService);
+ timing.end();
+
+ new GenericSignatureRewriter(
+ appView, prefixRewritingNamingLens, genericContextBuilderBeforeFinalMerging)
+ .run(appView.appInfo().classes(), executorService);
+
assert appView.checkForTesting(
() ->
!options.isShrinking()
@@ -822,16 +845,6 @@
.isValid())
: "Could not validate generic signatures";
- NamingLens prefixRewritingNamingLens =
- PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
-
- timing.begin("MinifyKotlinMetadata");
- new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForR8(executorService);
- timing.end();
-
- new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
- .run(appView.appInfo().classes(), executorService);
-
new DesugaredLibraryKeepRuleGenerator(appView, prefixRewritingNamingLens)
.runIfNecessary(timing);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index 124ea3f..f2502e2 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -11,29 +11,31 @@
import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.TraversalContinuation;
-import java.util.IdentityHashMap;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
public class AndroidApiReferenceLevelCache {
private static final int BUILD_CACHE_TRESHOLD = 20;
- private final Map<DexType, AndroidApiClass> apiTypeLookup;
- private final Map<DexReference, AndroidApiLevel> apiMemberLookup = new IdentityHashMap<>();
+ private final ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup;
+ private final ConcurrentHashMap<DexReference, AndroidApiLevel> apiMemberLookup =
+ new ConcurrentHashMap<>();
+ private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
private final AppView<?> appView;
private AndroidApiReferenceLevelCache(AppView<?> appView) {
- this.appView = appView;
- this.apiTypeLookup = new IdentityHashMap<>();
+ this(appView, new ConcurrentHashMap<>());
}
private AndroidApiReferenceLevelCache(
- AppView<?> appView, Map<DexType, AndroidApiClass> apiTypeLookup) {
+ AppView<?> appView, ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup) {
this.appView = appView;
this.apiTypeLookup = apiTypeLookup;
+ desugaredLibraryConfiguration = appView.options().desugaredLibraryConfiguration;
}
public static AndroidApiReferenceLevelCache create(AppView<?> appView) {
@@ -49,7 +51,7 @@
}
// The apiTypeLookup is build lazily except for the mocked api types that we define in tests
// externally.
- Map<DexType, AndroidApiClass> apiTypeLookup = new IdentityHashMap<>();
+ ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup = new ConcurrentHashMap<>();
appView
.options()
.apiModelingOptions()
@@ -78,6 +80,11 @@
if (!clazz.isLibraryClass()) {
return appView.options().minApiLevel;
}
+ if (desugaredLibraryConfiguration.isSupported(reference, appView)) {
+ // If we end up desugaring the reference, the library classes is bridged by j$ which is part
+ // of the program.
+ return appView.options().minApiLevel;
+ }
AndroidApiClass androidApiClass =
apiTypeLookup.computeIfAbsent(
contextType, type -> AndroidApiDatabaseBuilder.buildClass(type.asClassReference()));
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 40b630c..50362b0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -47,7 +47,7 @@
}
public final Origin origin;
- public DexType type;
+ public final DexType type;
public final ClassAccessFlags accessFlags;
public DexType superType;
public DexTypeList interfaces;
@@ -109,7 +109,7 @@
this.type = type;
setStaticFields(staticFields);
setInstanceFields(instanceFields);
- this.methodCollection = new MethodCollection(this, directMethods, virtualMethods);
+ this.methodCollection = MethodCollection.create(this, directMethods, virtualMethods);
this.nestHost = nestHost;
this.nestMembers = nestMembers;
assert nestMembers != null;
@@ -181,6 +181,10 @@
this.sourceFile = sourceFile;
}
+ public Iterable<DexClassAndField> classFields() {
+ return Iterables.transform(fields(), field -> DexClassAndField.create(this, field));
+ }
+
public Iterable<DexEncodedField> fields() {
return fields(Predicates.alwaysTrue());
}
@@ -204,6 +208,10 @@
return methodCollection;
}
+ public Iterable<DexClassAndMethod> classMethods() {
+ return Iterables.transform(methods(), method -> DexClassAndMethod.create(this, method));
+ }
+
public Iterable<DexEncodedMethod> methods() {
return methodCollection.methods();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
index 4aebe97..52cb7a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -24,6 +24,10 @@
public abstract AccessFlags<?> getAccessFlags();
+ public final DexAnnotationSet getAnnotations() {
+ return definition.annotations();
+ }
+
@Override
public DexType getContextType() {
return getHolderType();
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 53cbfe8..2f18405 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -66,6 +66,14 @@
return getReference().getParameters();
}
+ public DexAnnotationSet getParameterAnnotation(int index) {
+ return getParameterAnnotations().get(index);
+ }
+
+ public final ParameterAnnotationsList getParameterAnnotations() {
+ return getDefinition().getParameterAnnotations();
+ }
+
public DexProto getProto() {
return getReference().getProto();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index c2e8fae..25c339e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -100,19 +100,33 @@
public abstract DexReference getReference();
- private static <T extends DexDefinition> Stream<T> filter(
- Stream<DexDefinition> stream,
- Predicate<DexDefinition> pred,
- Function<DexDefinition, T> f) {
+ private static <T> Stream<T> filter(
+ Stream<DexDefinition> stream, Predicate<DexDefinition> pred, Function<DexDefinition, T> f) {
return stream.filter(pred).map(f);
}
public static Stream<DexEncodedField> filterDexEncodedField(Stream<DexDefinition> stream) {
- return filter(stream, DexDefinition::isDexEncodedField, DexDefinition::asDexEncodedField);
+ return filterDexEncodedField(stream, DexDefinition::asDexEncodedField);
+ }
+
+ public static <T> Stream<T> filterDexEncodedField(
+ Stream<DexDefinition> stream, Function<DexEncodedField, T> transform) {
+ return filter(
+ stream,
+ DexDefinition::isDexEncodedField,
+ field -> transform.apply(field.asDexEncodedField()));
}
public static Stream<DexEncodedMethod> filterDexEncodedMethod(Stream<DexDefinition> stream) {
- return filter(stream, DexDefinition::isDexEncodedMethod, DexDefinition::asDexEncodedMethod);
+ return filterDexEncodedMethod(stream, DexDefinition::asDexEncodedMethod);
+ }
+
+ public static <T> Stream<T> filterDexEncodedMethod(
+ Stream<DexDefinition> stream, Function<DexEncodedMethod, T> transform) {
+ return filter(
+ stream,
+ DexDefinition::isDexEncodedMethod,
+ field -> transform.apply(field.asDexEncodedMethod()));
}
public abstract boolean isStatic();
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 0dc90ad..2fdb279 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -389,6 +389,10 @@
return getReference().getParameters();
}
+ public DexType getReturnType() {
+ return getReference().getReturnType();
+ }
+
public DexMethodSignature getSignature() {
return new DexMethodSignature(getReference());
}
@@ -848,6 +852,10 @@
return classFileVersion;
}
+ public CfVersion getClassFileVersionOrElse(CfVersion defaultValue) {
+ return hasClassFileVersion() ? getClassFileVersion() : defaultValue;
+ }
+
public boolean hasClassFileVersion() {
checkIfObsolete();
return classFileVersion != null;
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 25cf31b..e8f5829 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1008,8 +1008,12 @@
public class ByteMembers extends LibraryMembers {
+ public final DexMethod byteValue =
+ createMethod(boxedByteType, createProto(byteType), "byteValue");
public final DexMethod toString =
createMethod(boxedByteType, createProto(stringType), "toString");
+ public final DexMethod valueOf =
+ createMethod(boxedByteType, createProto(boxedByteType, byteType), "valueOf");
private ByteMembers() {}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 5b3456c..64608dc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -672,25 +672,6 @@
methodCollection.replaceVirtualMethod(virtualMethod, replacement);
}
- public void replaceInterfaces(List<ClassTypeSignature> newInterfaces) {
- if (newInterfaces.isEmpty()) {
- return;
- }
- clearInterfaces();
- addExtraInterfaces(newInterfaces);
- }
-
- private void clearInterfaces() {
- interfaces = DexTypeList.empty();
- if (classSignature.hasSignature()) {
- classSignature =
- new ClassSignature(
- classSignature.formalTypeParameters,
- classSignature.superClassSignature,
- ImmutableList.of());
- }
- }
-
public void addExtraInterfaces(List<ClassTypeSignature> extraInterfaces) {
if (extraInterfaces.isEmpty()) {
return;
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 684dd7d..f399895 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -59,6 +59,10 @@
return values[index];
}
+ public DexType[] getBacking() {
+ return values;
+ }
+
public DexTypeList keepIf(Predicate<DexType> predicate) {
DexType[] filtered = ArrayUtils.filter(DexType[].class, values, predicate);
if (filtered != values) {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 419125d..920fbc1 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -15,6 +15,7 @@
import com.google.common.collect.ImmutableList;
import java.lang.reflect.GenericSignatureFormatError;
import java.nio.CharBuffer;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
@@ -293,6 +294,53 @@
public ClassSignature toObjectBoundWithSameFormals(ClassTypeSignature objectBound) {
return new ClassSignature(formalTypeParameters, objectBound, getEmptySuperInterfaces());
}
+
+ public List<FieldTypeSignature> getGenericArgumentsToSuperType(DexType type) {
+ assert hasSignature();
+ if (superClassSignature.type == type) {
+ return superClassSignature.typeArguments;
+ }
+ for (ClassTypeSignature superInterfaceSignature : superInterfaceSignatures) {
+ if (superInterfaceSignature.type == type) {
+ return superInterfaceSignature.typeArguments;
+ }
+ }
+ return null;
+ }
+
+ public static ClassSignatureBuilder builder() {
+ return new ClassSignatureBuilder();
+ }
+
+ public static class ClassSignatureBuilder {
+
+ private List<FormalTypeParameter> formalTypeParameters = new ArrayList<>();
+ private ClassTypeSignature superClassSignature = null;
+ private List<ClassTypeSignature> superInterfaceSignatures = new ArrayList<>();
+
+ private ClassSignatureBuilder() {}
+
+ public ClassSignatureBuilder addFormalTypeParameters(List<FormalTypeParameter> formals) {
+ formalTypeParameters.addAll(formals);
+ return this;
+ }
+
+ public ClassSignatureBuilder setSuperClassSignature(ClassTypeSignature superClassSignature) {
+ this.superClassSignature = superClassSignature;
+ return this;
+ }
+
+ public ClassSignatureBuilder addInterface(ClassTypeSignature iface) {
+ superInterfaceSignatures.add(iface);
+ return this;
+ }
+
+ public ClassSignature build() {
+ ClassSignature classSignature =
+ new ClassSignature(formalTypeParameters, superClassSignature, superInterfaceSignatures);
+ return classSignature;
+ }
+ }
}
private static class InvalidClassSignature extends ClassSignature {
@@ -585,7 +633,7 @@
return null;
}
List<FieldTypeSignature> rewrittenArguments =
- visitor.visitTypeArguments(visitedType, typeArguments);
+ visitor.visitTypeArguments(type, visitedType, typeArguments);
ClassTypeSignature rewrittenOuter = null;
if (enclosingTypeSignature != null) {
rewrittenOuter = visitor.visitEnclosing(enclosingTypeSignature, this);
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
index 00534ba..1b1eabf 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
@@ -6,8 +6,10 @@
import static com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext.empty;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.utils.WorkList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -25,26 +27,26 @@
private static class TypeParameterSubstitutions {
- private final Map<String, DexType> parametersWithBounds;
+ private final Map<String, FieldTypeSignature> parametersWithBounds;
- private TypeParameterSubstitutions(Map<String, DexType> parametersWithBounds) {
+ private TypeParameterSubstitutions(Map<String, FieldTypeSignature> parametersWithBounds) {
this.parametersWithBounds = parametersWithBounds;
}
private static TypeParameterSubstitutions create(List<FormalTypeParameter> formals) {
- Map<String, DexType> map = new IdentityHashMap<>();
+ Map<String, FieldTypeSignature> map = new HashMap<>();
formals.forEach(
formal -> {
- DexType bound = null;
if (formal.getClassBound() != null
&& formal.getClassBound().hasSignature()
&& formal.getClassBound().isClassTypeSignature()) {
- bound = formal.getClassBound().asClassTypeSignature().type;
+ map.put(formal.getName(), formal.getClassBound());
} else if (!formal.getInterfaceBounds().isEmpty()
&& formal.getInterfaceBounds().get(0).isClassTypeSignature()) {
- bound = formal.getInterfaceBounds().get(0).asClassTypeSignature().type;
+ map.put(formal.getName(), formal.getInterfaceBounds().get(0));
+ } else {
+ map.put(formal.getName(), null);
}
- map.put(formal.getName(), bound);
});
return new TypeParameterSubstitutions(map);
}
@@ -55,11 +57,11 @@
private static final TypeParameterContext EMPTY =
new TypeParameterContext(Collections.emptyMap(), Collections.emptySet());
- private final Map<String, DexType> prunedParametersWithBounds;
+ private final Map<String, FieldTypeSignature> prunedParametersWithBounds;
private final Set<String> liveParameters;
private TypeParameterContext(
- Map<String, DexType> prunedParametersWithBounds, Set<String> liveParameters) {
+ Map<String, FieldTypeSignature> prunedParametersWithBounds, Set<String> liveParameters) {
this.prunedParametersWithBounds = prunedParametersWithBounds;
this.liveParameters = liveParameters;
}
@@ -81,7 +83,7 @@
return liveParameters.contains(parameterName);
}
- public DexType getPrunedSubstitution(String parameterName) {
+ public FieldTypeSignature getPrunedSubstitution(String parameterName) {
assert !isLiveParameter(parameterName);
return prunedParametersWithBounds.get(parameterName);
}
@@ -93,7 +95,7 @@
HashSet<String> newLiveParameters = new HashSet<>();
newLiveParameters.addAll(liveParameters);
newLiveParameters.addAll(typeParameters);
- HashMap<String, DexType> newPruned = new HashMap<>();
+ HashMap<String, FieldTypeSignature> newPruned = new HashMap<>();
prunedParametersWithBounds.forEach(
(name, type) -> {
if (!typeParameters.contains(name)) {
@@ -103,11 +105,12 @@
return new TypeParameterContext(newPruned, newLiveParameters);
}
- public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) {
+ public TypeParameterContext addPrunedSubstitutions(
+ Map<String, FieldTypeSignature> substitutions) {
if (substitutions.isEmpty()) {
return this;
}
- HashMap<String, DexType> newPruned = new HashMap<>();
+ HashMap<String, FieldTypeSignature> newPruned = new HashMap<>();
newPruned.putAll(prunedParametersWithBounds);
newPruned.putAll(substitutions);
HashSet<String> newLiveParameters = new HashSet<>();
@@ -121,38 +124,6 @@
}
}
- public static class AlwaysLiveTypeParameterContext extends TypeParameterContext {
-
- private AlwaysLiveTypeParameterContext() {
- super(Collections.emptyMap(), Collections.emptySet());
- }
-
- public static AlwaysLiveTypeParameterContext create() {
- return new AlwaysLiveTypeParameterContext();
- }
-
- @Override
- public boolean isLiveParameter(String parameterName) {
- return true;
- }
-
- @Override
- public DexType getPrunedSubstitution(String parameterName) {
- assert false;
- return null;
- }
-
- @Override
- public TypeParameterContext addLiveParameters(Collection<String> typeParameters) {
- return this;
- }
-
- @Override
- public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) {
- return this;
- }
- }
-
private GenericSignatureContextBuilder(
Map<DexReference, TypeParameterSubstitutions> formalsInfo,
Map<DexReference, DexReference> enclosingInfo) {
@@ -160,7 +131,7 @@
this.enclosingInfo = enclosingInfo;
}
- public static GenericSignatureContextBuilder create(List<DexProgramClass> programClasses) {
+ public static GenericSignatureContextBuilder create(Collection<DexProgramClass> programClasses) {
Map<DexReference, TypeParameterSubstitutions> formalsInfo = new IdentityHashMap<>();
Map<DexReference, DexReference> enclosingInfo = new IdentityHashMap<>();
programClasses.forEach(
@@ -201,9 +172,27 @@
return new GenericSignatureContextBuilder(formalsInfo, enclosingInfo);
}
+ public static GenericSignatureContextBuilder createForSingleClass(
+ AppView<?> appView, DexProgramClass clazz) {
+ WorkList<DexProgramClass> workList = WorkList.newIdentityWorkList(clazz);
+ while (workList.hasNext()) {
+ DexProgramClass current = workList.next();
+ DexClass outer = null;
+ if (current.getEnclosingMethodAttribute() != null) {
+ outer = appView.definitionFor(current.getEnclosingMethodAttribute().getEnclosingType());
+ } else if (current.getInnerClassAttributeForThisClass() != null) {
+ outer = appView.definitionFor(current.getInnerClassAttributeForThisClass().getOuter());
+ }
+ if (outer != null && outer.isProgramClass()) {
+ workList.addIfNotSeen(outer.asProgramClass());
+ }
+ }
+ return create(workList.getSeenSet());
+ }
+
public TypeParameterContext computeTypeParameterContext(
AppView<?> appView, DexReference reference, Predicate<DexType> wasPruned) {
- assert !wasPruned.test(reference.getContextType());
+ assert !wasPruned.test(reference.getContextType()) : "Building context for pruned type";
return computeTypeParameterContext(appView, reference, wasPruned, false);
}
@@ -218,27 +207,42 @@
DexType contextType = reference.getContextType();
// TODO(b/187035453): We should visit generic signatures in the enqueuer.
DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(contextType);
- boolean prunedHere = seenPruned || clazz == null;
- if (appView.hasLiveness()
- && appView.withLiveness().appInfo().getMissingClasses().contains(contextType)) {
- prunedHere = seenPruned;
- }
+ boolean prunedHere = seenPruned;
+ // If the class cannot be looked up and it is not missing it was pruned here.
+ prunedHere |=
+ clazz == null
+ && appView.hasLiveness()
+ && !appView.withLiveness().appInfo().getMissingClasses().contains(contextType);
// Lookup the formals in the enclosing context.
+ TypeParameterSubstitutions formalsInfo = this.formalsInfo.get(contextType);
+ // If formals has been pruned then the context is also pruned here.
+ prunedHere |=
+ clazz != null
+ && formalsInfo != null
+ && !formalsInfo.parametersWithBounds.isEmpty()
+ && clazz.getClassSignature().getFormalTypeParameters().isEmpty();
+ DexReference enclosingReference = enclosingInfo.get(contextType);
TypeParameterContext typeParameterContext =
computeTypeParameterContext(
appView,
- enclosingInfo.get(contextType),
+ enclosingReference,
wasPruned,
prunedHere
- || hasPrunedRelationship(
- appView, enclosingInfo.get(contextType), contextType, wasPruned))
+ || hasPrunedRelationship(appView, enclosingReference, contextType, wasPruned))
// Add formals for the context
- .combine(formalsInfo.get(contextType), prunedHere);
+ .combine(formalsInfo, prunedHere);
if (!reference.isDexMethod()) {
return typeParameterContext;
}
- prunedHere = prunedHere || clazz == null || clazz.lookupMethod(reference.asDexMethod()) == null;
- return typeParameterContext.combine(formalsInfo.get(reference), prunedHere);
+ TypeParameterSubstitutions methodFormals = this.formalsInfo.get(reference);
+ if (clazz != null && !prunedHere) {
+ DexEncodedMethod method = clazz.lookupMethod(reference.asDexMethod());
+ prunedHere =
+ method == null
+ || (!methodFormals.parametersWithBounds.isEmpty()
+ && method.getGenericSignature().getFormalTypeParameters().isEmpty());
+ }
+ return typeParameterContext.combine(methodFormals, prunedHere);
}
public boolean hasPrunedRelationship(
@@ -257,15 +261,9 @@
}
// TODO(b/187035453): We should visit generic signatures in the enqueuer.
DexClass enclosingClass =
- appView
- .appInfo()
- .definitionForWithoutExistenceAssert(
- appView.graphLens().lookupClassType(enclosingReference.getContextType()));
+ appView.appInfo().definitionForWithoutExistenceAssert(enclosingReference.getContextType());
DexClass enclosedClass =
- appView
- .appInfo()
- .definitionForWithoutExistenceAssert(
- appView.graphLens().lookupClassType(enclosedClassType));
+ appView.appInfo().definitionForWithoutExistenceAssert(enclosedClassType);
if (enclosingClass == null || enclosedClass == null) {
return true;
}
@@ -279,12 +277,12 @@
}
}
- public boolean hasGenericTypeVariables(
+ public static boolean hasGenericTypeVariables(
AppView<?> appView, DexType type, Predicate<DexType> wasPruned) {
if (wasPruned.test(type)) {
return false;
}
- DexClass clazz = appView.definitionFor(appView.graphLens().lookupClassType(type));
+ DexClass clazz = appView.definitionFor(type);
if (clazz == null || clazz.isNotProgramClass() || clazz.getClassSignature().isInvalid()) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
index 03f4259..1946512 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -234,8 +234,7 @@
if (context.superType == appView.dexItemFactory().objectType
&& classSignature.superClassSignature().hasNoSignature()) {
// We represent no signature as object.
- } else if (context.superType
- != appView.graphLens().lookupClassType(classSignature.superClassSignature().type())) {
+ } else if (context.superType != classSignature.superClassSignature().type()) {
assert mode.doNotVerify() : "Super type inconsistency in generic signature";
return INVALID_SUPER_TYPE;
}
@@ -393,10 +392,7 @@
}
}
// TODO(b/187035453): We should visit generic signatures in the enqueuer.
- DexClass clazz =
- appView
- .appInfo()
- .definitionForWithoutExistenceAssert(appView.graphLens().lookupClassType(type));
+ DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(type);
if (clazz == null) {
// We do not know if the application of arguments works or not.
return VALID;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index 56a6496..12ca041 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -26,6 +26,8 @@
private final BiPredicate<DexType, DexType> enclosingPruned;
private final Predicate<DexType> hasGenericTypeParameters;
private final AppView<?> appView;
+ private final ClassTypeSignature objectArgument;
+ private boolean makeAllTypeArgumentsObject = false;
private GenericSignaturePartialTypeArgumentApplier(
AppView<?> appView,
@@ -36,6 +38,9 @@
this.typeParameterContext = typeParameterContext;
this.enclosingPruned = enclosingPruned;
this.hasGenericTypeParameters = hasGenericTypeParameters;
+ objectArgument =
+ new ClassTypeSignature(appView.dexItemFactory().objectType)
+ .asArgument(WildcardIndicator.NONE);
}
public static GenericSignaturePartialTypeArgumentApplier build(
@@ -78,6 +83,10 @@
@Override
public DexType visitType(DexType type) {
+ // It is important that the type is not looked up in the applier. The type-parameter context is
+ // a mapping from fully applied -> old references, which may seem a bit odd, but that is simply
+ // because we do not rewrite the signatures in lock step with rewriting the app.
+ // The actual lookup will be performed in the GenericSignatureTypeRewriter.
return type;
}
@@ -117,8 +126,12 @@
@Override
public List<FieldTypeSignature> visitTypeArguments(
- DexType type, List<FieldTypeSignature> typeArguments) {
- if (typeArguments.isEmpty() || !hasGenericTypeParameters.test(type)) {
+ DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
+ assert originalType == lookedUpType;
+ if (typeArguments.isEmpty()) {
+ return typeArguments;
+ }
+ if (!hasGenericTypeParameters.test(appView.graphLens().lookupType(originalType))) {
return getEmptyTypeArguments();
}
return ListUtils.mapOrElse(typeArguments, this::visitFieldTypeSignature);
@@ -145,7 +158,9 @@
@Override
public ClassTypeSignature visitEnclosing(
ClassTypeSignature enclosingSignature, ClassTypeSignature enclosedSignature) {
- if (enclosingPruned.test(enclosingSignature.type(), enclosedSignature.type())) {
+ DexType enclosingType = appView.graphLens().lookupType(enclosingSignature.type());
+ DexType enclosedType = appView.graphLens().lookupType(enclosedSignature.type());
+ if (enclosingPruned.test(enclosingType, enclosedType)) {
return null;
} else {
return enclosingSignature.visit(this);
@@ -208,15 +223,26 @@
return fieldSignature.asArrayTypeSignature().visit(this);
} else {
assert fieldSignature.isTypeVariableSignature();
- String typeVariableName = fieldSignature.asTypeVariableSignature().typeVariable();
- if (!typeParameterContext.isLiveParameter(typeVariableName)) {
- DexType substitution = typeParameterContext.getPrunedSubstitution(typeVariableName);
- if (substitution == null) {
- substitution = appView.dexItemFactory().objectType;
- }
- return new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
+ // TODO(b/b/191871201): If we track where type-variables are introduced, we can move this
+ // past typeParameterContext.isLiveParameter(typeVariableName) and get more precision.
+ if (makeAllTypeArgumentsObject) {
+ return objectArgument;
}
- return fieldSignature;
+ String typeVariableName = fieldSignature.asTypeVariableSignature().typeVariable();
+ if (typeParameterContext.isLiveParameter(typeVariableName)) {
+ return fieldSignature;
+ }
+ FieldTypeSignature substitution =
+ typeParameterContext.getPrunedSubstitution(typeVariableName);
+ if (substitution == null) {
+ return objectArgument;
+ }
+ makeAllTypeArgumentsObject = true;
+ substitution = visitFieldTypeSignature(substitution);
+ makeAllTypeArgumentsObject = false;
+ return substitution.isArgument()
+ ? substitution
+ : substitution.asArgument(WildcardIndicator.NONE);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index b111c57..9d54705 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -146,7 +146,7 @@
@Override
public List<FieldTypeSignature> visitTypeArguments(
- DexType type, List<FieldTypeSignature> typeArguments) {
+ DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
if (typeArguments.isEmpty()) {
return typeArguments;
}
@@ -207,7 +207,7 @@
}
sb.append(".").append(innerClassName);
}
- visitTypeArguments(null, classTypeSignature.typeArguments);
+ visitTypeArguments(null, null, classTypeSignature.typeArguments);
if (!printingOuter) {
sb.append(";");
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 332aa8d..7f224bb 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -29,26 +29,31 @@
private final DexProgramClass context;
private final ClassTypeSignature objectTypeSignature;
+ private final Predicate<DexType> hasGenericTypeVariables;
- public GenericSignatureTypeRewriter(AppView<?> appView, DexProgramClass context) {
+ public GenericSignatureTypeRewriter(
+ AppView<?> appView, DexProgramClass context, Predicate<DexType> hasGenericTypeVariables) {
this(
appView.dexItemFactory(),
appView.appInfo().hasLiveness()
? appView.appInfo().withLiveness()::wasPruned
: alwaysFalse(),
appView.graphLens()::lookupType,
- context);
+ context,
+ hasGenericTypeVariables);
}
public GenericSignatureTypeRewriter(
DexItemFactory factory,
Predicate<DexType> wasPruned,
Function<DexType, DexType> lookupType,
- DexProgramClass context) {
+ DexProgramClass context,
+ Predicate<DexType> hasGenericTypeVariables) {
this.factory = factory;
this.wasPruned = wasPruned;
this.lookupType = lookupType;
this.context = context;
+ this.hasGenericTypeVariables = hasGenericTypeVariables;
objectTypeSignature = new ClassTypeSignature(factory.objectType, getEmptyTypeArguments());
}
@@ -138,10 +143,13 @@
@Override
public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
+ if (context.superType == factory.objectType) {
+ return classTypeSignature.type == factory.objectType
+ ? classTypeSignature
+ : objectTypeSignature;
+ }
ClassTypeSignature rewritten = classTypeSignature.visit(this);
- return rewritten == null || rewritten.type() == context.type
- ? objectTypeSignature
- : rewritten;
+ return rewritten == null ? objectTypeSignature : rewritten;
}
@Override
@@ -239,10 +247,17 @@
@Override
public List<FieldTypeSignature> visitTypeArguments(
- DexType type, List<FieldTypeSignature> typeArguments) {
+ DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
+ assert lookedUpType != null;
if (typeArguments.isEmpty()) {
return typeArguments;
}
+ // If the original type has been pruned it must be because the old type has been merged into
+ // the looked up type. We can therefore not guarantee the type arguments to be consistent and
+ // have to remove them.
+ if (wasPruned.test(originalType) || !hasGenericTypeVariables.test(lookedUpType)) {
+ return getEmptyTypeArguments();
+ }
return ListUtils.mapOrElse(
typeArguments,
fieldTypeSignature -> {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
index 59cd879..a619129 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
@@ -144,7 +144,7 @@
@Override
public List<FieldTypeSignature> visitTypeArguments(
- DexType type, List<FieldTypeSignature> typeArguments) {
+ DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
typeArguments.forEach(this::visitFieldTypeSignature);
return typeArguments;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
index b321ad1..37b3cbe 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
@@ -84,7 +84,7 @@
}
default List<FieldTypeSignature> visitTypeArguments(
- DexType type, List<FieldTypeSignature> typeArguments) {
+ DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
throw new Unreachable("Implement if visited");
}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index a07a380..8e436ba 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -89,8 +89,9 @@
return rewritings.getOrDefault(reboundReference, reboundReference);
}
- public R getRewrittenReboundReference(Map<R, R> rewritings) {
- return rewritings.getOrDefault(reboundReference, reboundReference);
+ public R getRewrittenReboundReference(Function<R, R> rewritings) {
+ R rewrittenReboundReference = rewritings.apply(reboundReference);
+ return rewrittenReboundReference != null ? rewrittenReboundReference : reboundReference;
}
abstract static class Builder<R extends DexMember<?, R>, Self extends Builder<R, Self>> {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 2412ea7..3509b8c 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -2,6 +2,7 @@
import static com.google.common.base.Predicates.alwaysTrue;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import java.util.ArrayList;
@@ -24,7 +25,7 @@
private MethodCollectionBacking backing;
private DexEncodedMethod cachedClassInitializer = DexEncodedMethod.SENTINEL;
- public MethodCollection(
+ MethodCollection(
DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
this.holder = holder;
if (directMethods.length + virtualMethods.length > ARRAY_BACKING_THRESHOLD) {
@@ -36,6 +37,14 @@
backing.setVirtualMethods(virtualMethods);
}
+ public static MethodCollection create(
+ DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
+ if (InternalOptions.USE_METHOD_COLLECTION_CONCURRENCY_CHECKED) {
+ return new MethodCollectionConcurrencyChecked(holder, directMethods, virtualMethods);
+ }
+ return new MethodCollection(holder, directMethods, virtualMethods);
+ }
+
private void resetCaches() {
resetDirectMethodCaches();
resetVirtualMethodCaches();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
new file mode 100644
index 0000000..66be012
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
@@ -0,0 +1,338 @@
+// 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;
+
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MethodCollectionConcurrencyChecked extends MethodCollection {
+ private AtomicInteger readCount = new AtomicInteger();
+ private AtomicInteger writeCount = new AtomicInteger();
+
+ MethodCollectionConcurrencyChecked(
+ DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
+ super(holder, directMethods, virtualMethods);
+ }
+
+ private boolean assertReadEntry() {
+ assert writeCount.get() == 0;
+ assert readCount.incrementAndGet() >= 1;
+ return true;
+ }
+
+ private boolean assertReadExit() {
+ assert readCount.decrementAndGet() >= 0;
+ assert writeCount.get() == 0;
+ return true;
+ }
+
+ private boolean assertWriteEntry() {
+ assert readCount.get() == 0;
+ assert writeCount.incrementAndGet() == 1;
+ return true;
+ }
+
+ private boolean assertWriteExit() {
+ assert writeCount.decrementAndGet() == 0;
+ assert readCount.get() == 0;
+ return true;
+ }
+
+ @Override
+ public boolean hasDirectMethods(Predicate<DexEncodedMethod> predicate) {
+ assert assertReadEntry();
+ boolean result = super.getDirectMethod(predicate) != null;
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public boolean hasVirtualMethods(Predicate<DexEncodedMethod> predicate) {
+ assert assertReadEntry();
+ boolean result = super.getVirtualMethod(predicate) != null;
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public int numberOfDirectMethods() {
+ assert assertReadEntry();
+ int result = super.numberOfDirectMethods();
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public int numberOfVirtualMethods() {
+ assert assertReadEntry();
+ int result = super.numberOfVirtualMethods();
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public int size() {
+ assert assertReadEntry();
+ int result = super.size();
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
+ assert assertReadEntry();
+ TraversalContinuation result = super.traverse(fn);
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public void forEachMethodMatching(
+ Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+ assert assertReadEntry();
+ super.forEachMethodMatching(predicate, consumer);
+ assert assertReadExit();
+ }
+
+ @Override
+ public void forEachDirectMethodMatching(
+ Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+ assert assertReadEntry();
+ super.forEachDirectMethodMatching(predicate, consumer);
+ assert assertReadExit();
+ }
+
+ @Override
+ public void forEachVirtualMethodMatching(
+ Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+ assert assertReadEntry();
+ super.forEachVirtualMethodMatching(predicate, consumer);
+ assert assertReadExit();
+ }
+
+ @Override
+ public Iterable<DexEncodedMethod> methods() {
+ // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+ return super.methods();
+ }
+
+ @Override
+ public Iterable<DexEncodedMethod> directMethods() {
+ // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+ return super.directMethods();
+ }
+
+ @Override
+ public Iterable<DexEncodedMethod> virtualMethods() {
+ // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+ return super.virtualMethods();
+ }
+
+ @Override
+ public DexEncodedMethod getMethod(DexMethod method) {
+ assert assertReadEntry();
+ DexEncodedMethod result = super.getMethod(method);
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
+ assert assertReadEntry();
+ DexEncodedMethod result = super.getMethod(predicate);
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public DexEncodedMethod getDirectMethod(DexMethod method) {
+ assert assertReadEntry();
+ DexEncodedMethod result = super.getDirectMethod(method);
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
+ assert assertReadEntry();
+ DexEncodedMethod result = super.getDirectMethod(predicate);
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public DexEncodedMethod getVirtualMethod(DexMethod method) {
+ assert assertReadEntry();
+ DexEncodedMethod result = super.getVirtualMethod(method);
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
+ assert assertReadEntry();
+ DexEncodedMethod result = super.getVirtualMethod(predicate);
+ assert assertReadExit();
+ return result;
+ }
+
+ @Override
+ public void addMethod(DexEncodedMethod method) {
+ assert assertWriteEntry();
+ super.addMethod(method);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void addVirtualMethod(DexEncodedMethod virtualMethod) {
+ assert assertWriteEntry();
+ super.addVirtualMethod(virtualMethod);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void addDirectMethod(DexEncodedMethod directMethod) {
+ assert assertWriteEntry();
+ super.addDirectMethod(directMethod);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public DexEncodedMethod replaceDirectMethod(
+ DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ DexEncodedMethod result = super.replaceDirectMethod(method, replacement);
+ assert assertWriteExit();
+ return result;
+ }
+
+ @Override
+ public DexEncodedMethod replaceVirtualMethod(
+ DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ DexEncodedMethod result = super.replaceVirtualMethod(method, replacement);
+ assert assertWriteExit();
+ return result;
+ }
+
+ @Override
+ public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ super.replaceMethods(replacement);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ super.replaceDirectMethods(replacement);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ super.replaceVirtualMethods(replacement);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ super.replaceAllDirectMethods(replacement);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ super.replaceAllVirtualMethods(replacement);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+ DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ assert assertWriteEntry();
+ DexEncodedMethod result = super.replaceDirectMethodWithVirtualMethod(method, replacement);
+ assert assertWriteExit();
+ return result;
+ }
+
+ @Override
+ public void addDirectMethods(Collection<DexEncodedMethod> methods) {
+ assert assertWriteEntry();
+ super.addDirectMethods(methods);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void clearDirectMethods() {
+ assert assertWriteEntry();
+ super.clearDirectMethods();
+ assert assertWriteExit();
+ }
+
+ @Override
+ public DexEncodedMethod removeMethod(DexMethod method) {
+ assert assertWriteEntry();
+ DexEncodedMethod result = super.removeMethod(method);
+ assert assertWriteExit();
+ return result;
+ }
+
+ @Override
+ public void removeMethods(Set<DexEncodedMethod> methods) {
+ assert assertWriteEntry();
+ super.removeMethods(methods);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void setDirectMethods(DexEncodedMethod[] methods) {
+ assert assertWriteEntry();
+ super.setDirectMethods(methods);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+ assert assertWriteEntry();
+ super.addVirtualMethods(methods);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void clearVirtualMethods() {
+ assert assertWriteEntry();
+ super.clearVirtualMethods();
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void setVirtualMethods(DexEncodedMethod[] methods) {
+ assert assertWriteEntry();
+ super.setVirtualMethods(methods);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+ assert assertWriteEntry();
+ super.virtualizeMethods(privateInstanceMethods);
+ assert assertWriteExit();
+ }
+
+ @Override
+ public void useSortedBacking() {
+ assert assertWriteEntry();
+ super.useSortedBacking();
+ assert assertWriteExit();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
index 81724fb..1b49b31 100644
--- a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
import java.util.Collections;
import java.util.Map;
+import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -33,7 +34,7 @@
protected static final Map<DexType, DexType> EMPTY_TYPE_MAP = Collections.emptyMap();
protected final BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap;
- protected final Map<DexMethod, DexMethod> methodMap;
+ protected final Function<DexMethod, DexMethod> methodMap;
protected final Map<DexType, DexType> typeMap;
// Map that stores the new signature of methods that have been affected by class merging, unused
@@ -84,11 +85,20 @@
Map<DexMethod, DexMethod> methodMap,
Map<DexType, DexType> typeMap,
BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
- super(appView);
+ this(appView, fieldMap, methodMap::get, typeMap, newMethodSignatures);
assert !typeMap.isEmpty()
|| !methodMap.isEmpty()
|| !fieldMap.isEmpty()
|| isLegitimateToHaveEmptyMappings();
+ }
+
+ public NestedGraphLens(
+ AppView<?> appView,
+ BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+ Function<DexMethod, DexMethod> methodMap,
+ Map<DexType, DexType> typeMap,
+ BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
+ super(appView);
this.fieldMap = fieldMap;
this.methodMap = methodMap;
this.typeMap = typeMap;
@@ -199,7 +209,7 @@
} else {
// TODO(b/168282032): We should always have the rebound reference, so this should become
// unreachable.
- DexMethod newMethod = methodMap.get(previous.getReference());
+ DexMethod newMethod = methodMap.apply(previous.getReference());
if (newMethod == null) {
return previous;
}
@@ -306,13 +316,6 @@
.append(" -> ")
.append(to.getTypeName())
.append(System.lineSeparator()));
- methodMap.forEach(
- (from, to) ->
- builder
- .append(from.toSourceString())
- .append(" -> ")
- .append(to.toSourceString())
- .append(System.lineSeparator()));
fieldMap.forEachManyToOneMapping(
(fromSet, to) -> {
builder.append(
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 254f5a4..946bdbc 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -376,7 +376,7 @@
}
public void injectInterfaces(
- DexDefinitionSupplier definitions, DexProgramClass clazz, List<DexClass> newInterfaces) {
+ DexDefinitionSupplier definitions, DexProgramClass clazz, Set<DexClass> newInterfaces) {
for (DexClass newInterface : newInterfaces) {
populateInstantiatedHierarchy(definitions, newInterface.type, clazz);
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 28d6be7..c9788bd 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -8,6 +8,8 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -30,33 +32,33 @@
converter.registerCallbackIfRequired(method);
}
- private void traceInvoke(DexMethod invokedMethod) {
- converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod);
+ private void traceInvoke(DexMethod invokedMethod, Type invokeType, ProgramMethod context) {
+ converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod, invokeType, context);
}
@Override
public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
- this.traceInvoke(invokedMethod);
+ this.traceInvoke(invokedMethod, Type.STATIC, context);
}
@Override
public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
- this.traceInvoke(invokedMethod);
+ this.traceInvoke(invokedMethod, Type.DIRECT, context);
}
@Override
public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
- this.traceInvoke(invokedMethod);
+ this.traceInvoke(invokedMethod, Type.INTERFACE, context);
}
@Override
public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
- this.traceInvoke(invokedMethod);
+ this.traceInvoke(invokedMethod, Type.SUPER, context);
}
@Override
public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
- this.traceInvoke(invokedMethod);
+ this.traceInvoke(invokedMethod, Invoke.Type.VIRTUAL, context);
}
public ProgramMethodSet generateCallbackMethods() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index db0178c..7483522 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -24,6 +24,7 @@
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.graph.ProgramMember;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger;
@@ -325,6 +326,8 @@
mergeInterfaces();
mergeFields();
mergeMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
+ group.getTarget().clearClassSignature();
+ group.getTarget().forEachProgramMember(ProgramMember::clearGenericSignature);
}
public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 89dcd0a..eea0202 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.NoDifferentApiReferenceLevel;
import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
+import com.android.tools.r8.horizontalclassmerging.policies.NoFailedResolutionTargets;
import com.android.tools.r8.horizontalclassmerging.policies.NoIllegalInlining;
import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
@@ -116,6 +117,7 @@
new NoAnnotationClasses(),
new NoDirectRuntimeTypeChecks(appView, mode, runtimeTypeCheckInfo),
new NoEnums(appView),
+ new NoFailedResolutionTargets(appView),
new NoInterfaces(appView, mode),
new NoInnerClasses(),
new NoInstanceFieldAnnotations(),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFailedResolutionTargets.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFailedResolutionTargets.java
new file mode 100644
index 0000000..65af78c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFailedResolutionTargets.java
@@ -0,0 +1,37 @@
+// 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.horizontalclassmerging.policies;
+
+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.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+// TODO(b/192821424): Can be removed if handled.
+public class NoFailedResolutionTargets extends SingleClassPolicy {
+
+ private final Set<DexType> failedResolutionHolders;
+
+ public NoFailedResolutionTargets(AppView<AppInfoWithLiveness> appView) {
+ failedResolutionHolders = Sets.newIdentityHashSet();
+ for (DexMethod method : appView.appInfo().getFailedMethodResolutionTargets()) {
+ failedResolutionHolders.add(method.holder);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return "NoFailedResolutionTargets";
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ return !failedResolutionHolders.contains(program.getType());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
deleted file mode 100644
index 2c88a60..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
+++ /dev/null
@@ -1,48 +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.ir.analysis.inlining;
-
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-/** Constraint that is satisfied if a specific argument is always false. */
-public class BooleanFalseSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
- private BooleanFalseSimpleInliningConstraint(int argumentIndex) {
- super(argumentIndex);
- }
-
- static BooleanFalseSimpleInliningConstraint create(
- int argumentIndex, SimpleInliningConstraintFactory witness) {
- assert witness != null;
- return new BooleanFalseSimpleInliningConstraint(argumentIndex);
- }
-
- @Override
- public boolean isBooleanFalse() {
- return true;
- }
-
- @Override
- public boolean isSatisfied(InvokeMethod invoke) {
- Value argument = getArgument(invoke);
- assert argument.getType().isInt();
- return argument.isConstBoolean(false);
- }
-
- @Override
- public SimpleInliningConstraint fixupAfterRemovingThisParameter(
- SimpleInliningConstraintFactory factory) {
- assert getArgumentIndex() > 0;
- return factory.createBooleanFalseConstraint(getArgumentIndex() - 1);
- }
-
- @Override
- public SimpleInliningConstraint rewrittenWithUnboxedArguments(
- IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
- return this;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
deleted file mode 100644
index 46c29b0..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
+++ /dev/null
@@ -1,48 +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.ir.analysis.inlining;
-
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-/** Constraint that is satisfied if a specific argument is always true. */
-public class BooleanTrueSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
- private BooleanTrueSimpleInliningConstraint(int argumentIndex) {
- super(argumentIndex);
- }
-
- static BooleanTrueSimpleInliningConstraint create(
- int argumentIndex, SimpleInliningConstraintFactory witness) {
- assert witness != null;
- return new BooleanTrueSimpleInliningConstraint(argumentIndex);
- }
-
- @Override
- public boolean isBooleanTrue() {
- return true;
- }
-
- @Override
- public boolean isSatisfied(InvokeMethod invoke) {
- Value argument = getArgument(invoke);
- assert argument.getType().isInt();
- return argument.isConstBoolean(true);
- }
-
- @Override
- public SimpleInliningConstraint fixupAfterRemovingThisParameter(
- SimpleInliningConstraintFactory factory) {
- assert getArgumentIndex() > 0;
- return factory.createBooleanTrueConstraint(getArgumentIndex() - 1);
- }
-
- @Override
- public SimpleInliningConstraint rewrittenWithUnboxedArguments(
- IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
- return this;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToBooleanSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToBooleanSimpleInliningConstraint.java
new file mode 100644
index 0000000..dae3231
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToBooleanSimpleInliningConstraint.java
@@ -0,0 +1,46 @@
+// 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.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is satisfied if a specific argument is always true. */
+public class EqualToBooleanSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+ private final boolean value;
+
+ private EqualToBooleanSimpleInliningConstraint(int argumentIndex, boolean value) {
+ super(argumentIndex);
+ this.value = value;
+ }
+
+ static EqualToBooleanSimpleInliningConstraint create(
+ int argumentIndex, boolean value, SimpleInliningConstraintFactory witness) {
+ assert witness != null;
+ return new EqualToBooleanSimpleInliningConstraint(argumentIndex, value);
+ }
+
+ @Override
+ public boolean isSatisfied(InvokeMethod invoke) {
+ Value argumentRoot = getArgument(invoke).getAliasedValue();
+ return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
+ && argumentRoot.getDefinition().asConstNumber().getBooleanValue() == value;
+ }
+
+ @Override
+ public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+ IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
+ return this;
+ }
+
+ @Override
+ SimpleInliningArgumentConstraint withArgumentIndex(
+ int argumentIndex, SimpleInliningConstraintFactory factory) {
+ return factory.createEqualToBooleanConstraint(argumentIndex, value);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
index a321670..236e57b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
@@ -4,18 +4,10 @@
package com.android.tools.r8.ir.analysis.inlining;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-public class EqualToNumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
- private final long rawValue;
+public class EqualToNumberSimpleInliningConstraint extends NumberSimpleInliningConstraint {
private EqualToNumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
- super(argumentIndex);
- this.rawValue = rawValue;
+ super(argumentIndex, rawValue);
}
static EqualToNumberSimpleInliningConstraint create(
@@ -25,22 +17,13 @@
}
@Override
- public boolean isSatisfied(InvokeMethod invoke) {
- Value argumentRoot = getArgument(invoke).getAliasedValue();
- return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
- && argumentRoot.getDefinition().asConstNumber().getRawValue() == rawValue;
+ boolean test(long argumentValue) {
+ return argumentValue == getRawValue();
}
@Override
- public SimpleInliningConstraint fixupAfterRemovingThisParameter(
- SimpleInliningConstraintFactory factory) {
- assert getArgumentIndex() > 0;
- return factory.createNumberConstraint(getArgumentIndex() - 1, rawValue);
- }
-
- @Override
- public SimpleInliningConstraint rewrittenWithUnboxedArguments(
- IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
- return this;
+ SimpleInliningArgumentConstraint withArgumentIndex(
+ int argumentIndex, SimpleInliningConstraintFactory factory) {
+ return factory.createEqualToNumberConstraint(argumentIndex, getRawValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
index 4739231..5f83db7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
@@ -4,18 +4,10 @@
package com.android.tools.r8.ir.analysis.inlining;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-public class NotEqualToNumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
- private final long rawValue;
+public class NotEqualToNumberSimpleInliningConstraint extends NumberSimpleInliningConstraint {
private NotEqualToNumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
- super(argumentIndex);
- this.rawValue = rawValue;
+ super(argumentIndex, rawValue);
}
static NotEqualToNumberSimpleInliningConstraint create(
@@ -25,22 +17,13 @@
}
@Override
- public boolean isSatisfied(InvokeMethod invoke) {
- Value argumentRoot = getArgument(invoke).getAliasedValue();
- return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
- && argumentRoot.getDefinition().asConstNumber().getRawValue() != rawValue;
+ boolean test(long argumentValue) {
+ return argumentValue != getRawValue();
}
@Override
- public SimpleInliningConstraint fixupAfterRemovingThisParameter(
- SimpleInliningConstraintFactory factory) {
- assert getArgumentIndex() > 0;
- return factory.createNotNumberConstraint(getArgumentIndex() - 1, rawValue);
- }
-
- @Override
- public SimpleInliningConstraint rewrittenWithUnboxedArguments(
- IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
- return this;
+ SimpleInliningArgumentConstraint withArgumentIndex(
+ int argumentIndex, SimpleInliningConstraintFactory factory) {
+ return factory.createNotEqualToNumberConstraint(argumentIndex, getRawValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
deleted file mode 100644
index 00288f8..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
+++ /dev/null
@@ -1,51 +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.ir.analysis.inlining;
-
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-/** Constraint that is satisfied if a specific argument is always non-null. */
-public class NotNullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
- private NotNullSimpleInliningConstraint(int argumentIndex) {
- super(argumentIndex);
- }
-
- static NotNullSimpleInliningConstraint create(
- int argumentIndex, SimpleInliningConstraintFactory witness) {
- assert witness != null;
- return new NotNullSimpleInliningConstraint(argumentIndex);
- }
-
- @Override
- public boolean isNotNull() {
- return true;
- }
-
- @Override
- public boolean isSatisfied(InvokeMethod invoke) {
- Value argument = getArgument(invoke);
- assert argument.getType().isReferenceType() : invoke;
- return argument.isNeverNull();
- }
-
- @Override
- public SimpleInliningConstraint fixupAfterRemovingThisParameter(
- SimpleInliningConstraintFactory factory) {
- assert getArgumentIndex() > 0;
- return factory.createNotNullConstraint(getArgumentIndex() - 1);
- }
-
- @Override
- public SimpleInliningConstraint rewrittenWithUnboxedArguments(
- IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
- if (unboxedArgumentIndices.contains(getArgumentIndex())) {
- return factory.createNotNumberConstraint(getArgumentIndex(), 0);
- }
- return this;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
index 5a0d3cf..13cc5b9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.ir.analysis.inlining;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -11,45 +14,52 @@
/** Constraint that is satisfied if a specific argument is always null. */
public class NullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
- private NullSimpleInliningConstraint(int argumentIndex) {
+ private final Nullability nullability;
+
+ private NullSimpleInliningConstraint(int argumentIndex, Nullability nullability) {
super(argumentIndex);
+ assert nullability.isDefinitelyNull() || nullability.isDefinitelyNotNull();
+ this.nullability = nullability;
}
static NullSimpleInliningConstraint create(
- int argumentIndex, SimpleInliningConstraintFactory witness) {
+ int argumentIndex, Nullability nullability, SimpleInliningConstraintFactory witness) {
assert witness != null;
- return new NullSimpleInliningConstraint(argumentIndex);
+ return new NullSimpleInliningConstraint(argumentIndex, nullability);
}
@Override
- public boolean isNull() {
- return true;
- }
+ public final boolean isSatisfied(InvokeMethod invoke) {
+ Value argument = getArgument(invoke);
+ TypeElement argumentType = argument.getType();
+ assert argumentType.isReferenceType();
- @Override
- public boolean isSatisfied(InvokeMethod invoke) {
+ if (argumentType.nullability() == nullability) {
+ return true;
+ }
+
// Take the root value to also deal with the following case, which may happen in dead code,
// where v1 is actually guaranteed to be null, despite the value's type being non-null:
// v0 <- ConstNumber 0
// v1 <- AssumeNotNull v0
- Value argumentRoot = getArgument(invoke).getAliasedValue();
- assert argumentRoot.getType().isReferenceType();
- return argumentRoot.getType().isDefinitelyNull();
- }
-
- @Override
- public SimpleInliningConstraint fixupAfterRemovingThisParameter(
- SimpleInliningConstraintFactory factory) {
- assert getArgumentIndex() > 0;
- return factory.createNullConstraint(getArgumentIndex() - 1);
+ return argument.isDefinedByInstructionSatisfying(Instruction::isAssume)
+ && argument.getAliasedValue().getType().nullability() == nullability;
}
@Override
public SimpleInliningConstraint rewrittenWithUnboxedArguments(
IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
if (unboxedArgumentIndices.contains(getArgumentIndex())) {
- return factory.createNumberConstraint(getArgumentIndex(), 0);
+ return nullability.isDefinitelyNull()
+ ? factory.createEqualToNumberConstraint(getArgumentIndex(), 0)
+ : factory.createNotEqualToNumberConstraint(getArgumentIndex(), 0);
}
return this;
}
+
+ @Override
+ SimpleInliningArgumentConstraint withArgumentIndex(
+ int argumentIndex, SimpleInliningConstraintFactory factory) {
+ return factory.createNullConstraint(argumentIndex, nullability);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NumberSimpleInliningConstraint.java
new file mode 100644
index 0000000..4f6ee2c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NumberSimpleInliningConstraint.java
@@ -0,0 +1,39 @@
+// 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.analysis.inlining;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+public abstract class NumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+ private final long rawValue;
+
+ NumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
+ super(argumentIndex);
+ this.rawValue = rawValue;
+ }
+
+ long getRawValue() {
+ return rawValue;
+ }
+
+ @Override
+ public final boolean isSatisfied(InvokeMethod invoke) {
+ Value argumentRoot = getArgument(invoke).getAliasedValue();
+ return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
+ && test(argumentRoot.getDefinition().asConstNumber().getRawValue());
+ }
+
+ abstract boolean test(long argumentValue);
+
+ @Override
+ public final SimpleInliningConstraint rewrittenWithUnboxedArguments(
+ IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
index 749d6f4..78bf329 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
@@ -15,6 +15,13 @@
this.argumentIndex = argumentIndex;
}
+ @Override
+ public final SimpleInliningConstraint fixupAfterRemovingThisParameter(
+ SimpleInliningConstraintFactory factory) {
+ assert getArgumentIndex() > 0;
+ return withArgumentIndex(getArgumentIndex() - 1, factory);
+ }
+
Value getArgument(InvokeMethod invoke) {
return invoke.getArgument(argumentIndex);
}
@@ -27,4 +34,7 @@
public boolean isArgumentConstraint() {
return true;
}
+
+ abstract SimpleInliningArgumentConstraint withArgumentIndex(
+ int argumentIndex, SimpleInliningConstraintFactory factory);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
index 82c3f7d..c9978cb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
@@ -19,14 +19,6 @@
return false;
}
- public boolean isBooleanFalse() {
- return false;
- }
-
- public boolean isBooleanTrue() {
- return false;
- }
-
public boolean isConjunction() {
return false;
}
@@ -47,14 +39,6 @@
return false;
}
- public boolean isNotNull() {
- return false;
- }
-
- public boolean isNull() {
- return false;
- }
-
public abstract boolean isSatisfied(InvokeMethod invoke);
public final SimpleInliningConstraint meet(SimpleInliningConstraint other) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
index 6d79b82..3d74f82 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
@@ -173,15 +173,15 @@
case EQ:
if (isZeroTest) {
if (argumentType.isReferenceType()) {
- return factory.createNullConstraint(argumentIndex);
+ return factory.createEqualToNullConstraint(argumentIndex);
}
if (argumentType.isBooleanType()) {
- return factory.createBooleanFalseConstraint(argumentIndex);
+ return factory.createEqualToFalseConstraint(argumentIndex);
}
} else if (argumentType.isPrimitiveType()) {
OptionalLong rawValue = getRawNumberValue(otherOperand);
if (rawValue.isPresent()) {
- return factory.createNumberConstraint(argumentIndex, rawValue.getAsLong());
+ return factory.createEqualToNumberConstraint(argumentIndex, rawValue.getAsLong());
}
}
return NeverSimpleInliningConstraint.getInstance();
@@ -189,15 +189,15 @@
case NE:
if (isZeroTest) {
if (argumentType.isReferenceType()) {
- return factory.createNotNullConstraint(argumentIndex);
+ return factory.createNotEqualToNullConstraint(argumentIndex);
}
if (argumentType.isBooleanType()) {
- return factory.createBooleanTrueConstraint(argumentIndex);
+ return factory.createEqualToTrueConstraint(argumentIndex);
}
} else if (argumentType.isPrimitiveType()) {
OptionalLong rawValue = getRawNumberValue(otherOperand);
if (rawValue.isPresent()) {
- return factory.createNotNumberConstraint(argumentIndex, rawValue.getAsLong());
+ return factory.createNotEqualToNumberConstraint(argumentIndex, rawValue.getAsLong());
}
}
return NeverSimpleInliningConstraint.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
index ed3cd55..5b60ccd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.ir.analysis.inlining;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNull;
+
+import com.android.tools.r8.ir.analysis.type.Nullability;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
@@ -11,78 +15,81 @@
public class SimpleInliningConstraintFactory {
// Immutable argument constraints for low argument indices to avoid overhead of ConcurrentHashMap.
- private final BooleanFalseSimpleInliningConstraint[] lowBooleanFalseConstraints =
- new BooleanFalseSimpleInliningConstraint[5];
- private final BooleanTrueSimpleInliningConstraint[] lowBooleanTrueConstraints =
- new BooleanTrueSimpleInliningConstraint[5];
- private final NotNullSimpleInliningConstraint[] lowNotNullConstraints =
- new NotNullSimpleInliningConstraint[5];
- private final NullSimpleInliningConstraint[] lowNullConstraints =
+ private final EqualToBooleanSimpleInliningConstraint[] lowEqualToFalseConstraints =
+ new EqualToBooleanSimpleInliningConstraint[5];
+ private final EqualToBooleanSimpleInliningConstraint[] lowEqualToTrueConstraints =
+ new EqualToBooleanSimpleInliningConstraint[5];
+ private final NullSimpleInliningConstraint[] lowNotEqualToNullConstraints =
+ new NullSimpleInliningConstraint[5];
+ private final NullSimpleInliningConstraint[] lowEqualToNullConstraints =
new NullSimpleInliningConstraint[5];
// Argument constraints for high argument indices.
- private final Map<Integer, BooleanFalseSimpleInliningConstraint> highBooleanFalseConstraints =
+ private final Map<Integer, EqualToBooleanSimpleInliningConstraint> highEqualToFalseConstraints =
new ConcurrentHashMap<>();
- private final Map<Integer, BooleanTrueSimpleInliningConstraint> highBooleanTrueConstraints =
+ private final Map<Integer, EqualToBooleanSimpleInliningConstraint> highEqualToTrueConstraints =
new ConcurrentHashMap<>();
- private final Map<Integer, NotNullSimpleInliningConstraint> highNotNullConstraints =
+ private final Map<Integer, NullSimpleInliningConstraint> highNotEqualToNullConstraints =
new ConcurrentHashMap<>();
- private final Map<Integer, NullSimpleInliningConstraint> highNullConstraints =
+ private final Map<Integer, NullSimpleInliningConstraint> highEqualToNullConstraints =
new ConcurrentHashMap<>();
public SimpleInliningConstraintFactory() {
- for (int i = 0; i < lowBooleanFalseConstraints.length; i++) {
- lowBooleanFalseConstraints[i] = BooleanFalseSimpleInliningConstraint.create(i, this);
+ for (int i = 0; i < lowEqualToFalseConstraints.length; i++) {
+ lowEqualToFalseConstraints[i] = EqualToBooleanSimpleInliningConstraint.create(i, false, this);
}
- for (int i = 0; i < lowBooleanTrueConstraints.length; i++) {
- lowBooleanTrueConstraints[i] = BooleanTrueSimpleInliningConstraint.create(i, this);
+ for (int i = 0; i < lowEqualToTrueConstraints.length; i++) {
+ lowEqualToTrueConstraints[i] = EqualToBooleanSimpleInliningConstraint.create(i, true, this);
}
- for (int i = 0; i < lowNotNullConstraints.length; i++) {
- lowNotNullConstraints[i] = NotNullSimpleInliningConstraint.create(i, this);
+ for (int i = 0; i < lowNotEqualToNullConstraints.length; i++) {
+ lowNotEqualToNullConstraints[i] =
+ NullSimpleInliningConstraint.create(i, definitelyNotNull(), this);
}
- for (int i = 0; i < lowNullConstraints.length; i++) {
- lowNullConstraints[i] = NullSimpleInliningConstraint.create(i, this);
+ for (int i = 0; i < lowEqualToNullConstraints.length; i++) {
+ lowEqualToNullConstraints[i] = NullSimpleInliningConstraint.create(i, definitelyNull(), this);
}
}
- public BooleanFalseSimpleInliningConstraint createBooleanFalseConstraint(int argumentIndex) {
- return createArgumentConstraint(
- argumentIndex,
- lowBooleanFalseConstraints,
- highBooleanFalseConstraints,
- () -> BooleanFalseSimpleInliningConstraint.create(argumentIndex, this));
+ public EqualToBooleanSimpleInliningConstraint createEqualToFalseConstraint(int argumentIndex) {
+ return createEqualToBooleanConstraint(argumentIndex, false);
}
- public BooleanTrueSimpleInliningConstraint createBooleanTrueConstraint(int argumentIndex) {
- return createArgumentConstraint(
- argumentIndex,
- lowBooleanTrueConstraints,
- highBooleanTrueConstraints,
- () -> BooleanTrueSimpleInliningConstraint.create(argumentIndex, this));
+ public EqualToBooleanSimpleInliningConstraint createEqualToTrueConstraint(int argumentIndex) {
+ return createEqualToBooleanConstraint(argumentIndex, true);
}
- public NotNullSimpleInliningConstraint createNotNullConstraint(int argumentIndex) {
+ public EqualToBooleanSimpleInliningConstraint createEqualToBooleanConstraint(
+ int argumentIndex, boolean value) {
return createArgumentConstraint(
argumentIndex,
- lowNotNullConstraints,
- highNotNullConstraints,
- () -> NotNullSimpleInliningConstraint.create(argumentIndex, this));
+ value ? lowEqualToTrueConstraints : lowEqualToFalseConstraints,
+ value ? highEqualToTrueConstraints : highEqualToFalseConstraints,
+ () -> EqualToBooleanSimpleInliningConstraint.create(argumentIndex, value, this));
}
- public NullSimpleInliningConstraint createNullConstraint(int argumentIndex) {
+ public NullSimpleInliningConstraint createEqualToNullConstraint(int argumentIndex) {
+ return createNullConstraint(argumentIndex, definitelyNull());
+ }
+
+ public NullSimpleInliningConstraint createNotEqualToNullConstraint(int argumentIndex) {
+ return createNullConstraint(argumentIndex, definitelyNotNull());
+ }
+
+ public NullSimpleInliningConstraint createNullConstraint(
+ int argumentIndex, Nullability nullability) {
return createArgumentConstraint(
argumentIndex,
- lowNullConstraints,
- highNullConstraints,
- () -> NullSimpleInliningConstraint.create(argumentIndex, this));
+ nullability.isDefinitelyNull() ? lowEqualToNullConstraints : lowNotEqualToNullConstraints,
+ nullability.isDefinitelyNull() ? highEqualToNullConstraints : highNotEqualToNullConstraints,
+ () -> NullSimpleInliningConstraint.create(argumentIndex, nullability, this));
}
- public NotEqualToNumberSimpleInliningConstraint createNotNumberConstraint(
+ public NotEqualToNumberSimpleInliningConstraint createNotEqualToNumberConstraint(
int argumentIndex, long rawValue) {
return NotEqualToNumberSimpleInliningConstraint.create(argumentIndex, rawValue, this);
}
- public EqualToNumberSimpleInliningConstraint createNumberConstraint(
+ public EqualToNumberSimpleInliningConstraint createEqualToNumberConstraint(
int argumentIndex, long rawValue) {
return EqualToNumberSimpleInliningConstraint.create(argumentIndex, rawValue, this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index fbca901..e78ade7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -32,6 +32,10 @@
this.knownToBeBoolean = knownToBeBoolean;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
public int getIndex() {
assert verifyIndex();
return index;
@@ -141,4 +145,25 @@
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
return false;
}
+
+ public static class Builder extends BuilderBase<Builder, Argument> {
+
+ private int index;
+ private boolean knownToBeBoolean;
+
+ public Builder setIndex(int index) {
+ this.index = index;
+ return this;
+ }
+
+ @Override
+ public Argument build() {
+ return amend(new Argument(outValue, index, knownToBeBoolean));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index a6a4704..e06df9e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -63,12 +63,14 @@
private Int2ReferenceMap<DebugLocalInfo> localsAtEntry;
- public boolean consistentBlockInstructions(boolean argumentsAllowed, boolean debug) {
+ public boolean consistentBlockInstructions(boolean argumentsAllowed, boolean debug, boolean ssa) {
for (Instruction instruction : getInstructions()) {
assert instruction.verifyValidPositionInfo(debug);
assert instruction.getBlock() == this;
assert !instruction.isArgument() || argumentsAllowed;
assert !instruction.isDebugLocalRead() || !instruction.getDebugValues().isEmpty();
+ assert !instruction.isInitClass()
+ || consistentInitClassInstruction(instruction.asInitClass(), ssa);
if (instruction.isMoveException()) {
assert instruction == entry();
for (BasicBlock pred : getPredecessors()) {
@@ -83,6 +85,17 @@
return true;
}
+ public boolean consistentInitClassInstruction(InitClass initClass, boolean ssa) {
+ if (!ssa) {
+ return true;
+ }
+ assert initClass.hasOutValue();
+ assert !initClass.outValue().hasDebugUsers();
+ assert !initClass.outValue().hasPhiUsers();
+ assert initClass.outValue().uniqueUsers().stream().allMatch(Instruction::isPop);
+ return true;
+ }
+
public boolean verifyTypes(AppView<?> appView, VerifyTypesHelper verifyTypesHelper) {
assert instructions.stream()
.allMatch(instruction -> instruction.verifyTypes(appView, verifyTypesHelper));
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index b02559a..b5e4027 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -18,12 +18,14 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
public class CheckCast extends Instruction {
@@ -42,6 +44,27 @@
return new Builder();
}
+ public boolean isRefiningStaticType(InternalOptions options) {
+ TypeElement inType = object().getType();
+ if (inType.isNullType()) {
+ // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+ // elimination may lead to verification errors. See b/123269162.
+ if (options.canHaveArtCheckCastVerifierBug()
+ && getType().isArrayType()
+ && getType().toBaseType(options.dexItemFactory()).isFloatType()) {
+ return true;
+ }
+ return false;
+ }
+ if (!inType.isClassType()) {
+ // Conservatively return true.
+ assert inType.isArrayType();
+ return true;
+ }
+ ClassTypeElement inClassType = inType.asClassType();
+ return type != inClassType.getClassType();
+ }
+
@Override
public int opcode() {
return Opcodes.CHECK_CAST;
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 202df23..0447c1a 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
@@ -13,7 +13,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
import com.android.tools.r8.ir.analysis.TypeChecker;
import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -577,7 +577,8 @@
}
public boolean isConsistentSSABeforeTypesAreCorrect() {
- assert isConsistentGraph();
+ assert isConsistentGraph(true);
+ assert consistentBlockInstructions(true);
assert consistentDefUseChains();
assert validThrowingInstructions();
assert noCriticalEdges();
@@ -585,21 +586,20 @@
return true;
}
- public boolean hasNoVerticallyMergedClasses(
- AppView<? extends AppInfoWithClassHierarchy> appView) {
- VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
- if (verticallyMergedClasses == null) {
+ public boolean hasNoMergedClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ MergedClassesCollection mergedClasses = appView.allMergedClasses();
+ if (mergedClasses == null) {
return true;
}
for (Instruction instruction : instructions()) {
if (instruction.outValue != null && instruction.outValue.getType().isClassType()) {
ClassTypeElement classTypeLattice = instruction.outValue.getType().asClassType();
- assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(classTypeLattice.getClassType());
+ assert !mergedClasses.hasBeenMergedIntoDifferentType(classTypeLattice.getClassType());
assert !classTypeLattice
.getInterfaces()
.anyMatch(
(itf, isKnown) -> {
- assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(itf);
+ assert !mergedClasses.hasBeenMergedIntoDifferentType(itf);
return false;
});
}
@@ -608,11 +608,15 @@
}
public boolean isConsistentGraph() {
+ return isConsistentGraph(false);
+ }
+
+ public boolean isConsistentGraph(boolean ssa) {
assert noColorsInUse();
assert consistentBlockNumbering();
assert consistentPredecessorSuccessors();
assert consistentCatchHandlers();
- assert consistentBlockInstructions();
+ assert consistentBlockInstructions(ssa);
assert consistentMetadata();
assert !allThrowingInstructionsHavePositions || computeAllThrowingInstructionsHavePositions();
return true;
@@ -804,12 +808,13 @@
return true;
}
- private boolean consistentBlockInstructions() {
+ private boolean consistentBlockInstructions(boolean ssa) {
boolean argumentsAllowed = true;
for (BasicBlock block : blocks) {
assert block.consistentBlockInstructions(
argumentsAllowed,
- options.debug || method().getOptimizationInfo().isReachabilitySensitive());
+ options.debug || method().getOptimizationInfo().isReachabilitySensitive(),
+ ssa);
argumentsAllowed = false;
}
return true;
@@ -1300,6 +1305,10 @@
}
}
+ public LinkedList<BasicBlock> getBlocks() {
+ return blocks;
+ }
+
/**
* Returns the set of blocks that are reachable from the given source. The source itself is only
* included if there is a path from the given block to itself.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 5517fb8..0332c6e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -102,6 +102,10 @@
return inValues().get(index);
}
+ public Value getFirstOperand() {
+ return getOperand(0);
+ }
+
public List<Value> inValues() {
return inValues;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index e2f81a9..e28ee89 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -51,6 +51,10 @@
return returnValue().getType();
}
+ public boolean hasReturnValue() {
+ return !isReturnVoid();
+ }
+
public Value returnValue() {
assert !isReturnVoid();
return inValues.get(0);
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 0266f4b..514002e 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
@@ -13,8 +13,6 @@
import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer.D8CfPostProcessingDesugaringEventConsumer;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -77,6 +75,11 @@
D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer =
CfInstructionDesugaringEventConsumer.createForD8(methodProcessor);
+ // TODO(b/191656218): Move upfront the loop and use maybe the class event consumer.
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ converter.ensureWrappersForL8(instructionDesugaringEventConsumer);
+ }
+
// Process the wave and wait for all IR processing to complete.
methodProcessor.newWave();
ThreadUtils.processItems(
@@ -111,13 +114,6 @@
classes = deferred;
}
-
- D8CfPostProcessingDesugaringEventConsumer eventConsumer =
- CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor, appView);
- methodProcessor.newWave();
- converter.postProcessDesugaring(eventConsumer);
- methodProcessor.awaitMethodProcessing();
- eventConsumer.finalizeDesugaring();
}
abstract void convertClass(
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 b73d9d7..b6b40f8 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
@@ -51,10 +51,12 @@
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
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;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceProcessor;
+import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
@@ -93,7 +95,6 @@
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
import com.android.tools.r8.position.MethodPosition;
@@ -112,7 +113,6 @@
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -226,12 +226,8 @@
assert options.desugarState.isOn();
this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
- this.interfaceMethodRewriter =
- options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
- ? null
- : new InterfaceMethodRewriter(appView, this);
- this.desugaredLibraryAPIConverter =
- new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
+ this.interfaceMethodRewriter = null;
+ this.desugaredLibraryAPIConverter = null;
this.covariantReturnTypeAnnotationTransformer = null;
this.dynamicTypeOptimization = null;
this.classInliner = null;
@@ -259,7 +255,7 @@
: CfInstructionDesugaringCollection.create(appView);
this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
this.interfaceMethodRewriter =
- options.isInterfaceMethodDesugaringEnabled()
+ options.isInterfaceMethodDesugaringEnabled() && appView.enableWholeProgramOptimizations()
? new InterfaceMethodRewriter(appView, this)
: null;
this.covariantReturnTypeAnnotationTransformer =
@@ -323,10 +319,7 @@
this.identifierNameStringMarker = null;
this.devirtualizer = null;
this.typeChecker = null;
- this.desugaredLibraryAPIConverter =
- appView.rewritePrefix.isRewriting()
- ? new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS)
- : null;
+ this.desugaredLibraryAPIConverter = null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumValueOptimizer = null;
@@ -367,9 +360,11 @@
D8NestBasedAccessDesugaring::clearNestAttributes);
}
- void postProcessDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
- CfPostProcessingDesugaringCollection.create(appView, instructionDesugaring.getRetargetingInfo())
- .postProcessingDesugaring(eventConsumer);
+ public void ensureWrappersForL8(
+ D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer) {
+ assert appView.options().isDesugaredLibraryCompilation();
+ instructionDesugaring.withDesugaredLibraryAPIConverter(
+ converter -> converter.ensureWrappersForL8(instructionDesugaringEventConsumer));
}
private void staticizeClasses(
@@ -394,22 +389,15 @@
}
}
- private void runInterfaceDesugaringProcessors(
+ private void runInterfaceDesugaringProcessorsForR8(
Flavor includeAllResources, ExecutorService executorService) throws ExecutionException {
assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
if (interfaceMethodRewriter != null) {
- interfaceMethodRewriter.runInterfaceDesugaringProcessors(
+ interfaceMethodRewriter.runInterfaceDesugaringProcessorsForR8(
this, includeAllResources, executorService);
}
}
- private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
- throws ExecutionException {
- if (enumUnboxer != null) {
- enumUnboxer.synthesizeUtilityMethods(this, executorService);
- }
- }
-
private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
if (covariantReturnTypeAnnotationTransformer != null) {
covariantReturnTypeAnnotationTransformer.process(builder);
@@ -422,30 +410,28 @@
workaroundAbstractMethodOnNonAbstractClassVerificationBug(
executor, OptimizationFeedbackIgnore.getInstance());
DexApplication application = appView.appInfo().app();
+ D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executor);
+
timing.begin("IR conversion");
- convertClasses(executor);
+ convertClasses(methodProcessor, executor);
reportNestDesugarDependencies();
clearNestAttributes();
- if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
- appView.setAppInfo(
- new AppInfo(
- appView.appInfo().getSyntheticItems().commit(application),
- appView.appInfo().getMainDexInfo()));
- application = appView.appInfo().app();
- }
+ application = commitPendingSyntheticItems(appView, application);
+
+ postProcessingDesugaringForD8(methodProcessor, executor);
+
+ application = commitPendingSyntheticItems(appView, application);
// Build a new application with jumbo string info,
Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
- runInterfaceDesugaringProcessors(ExcludeDexResources, executor);
if (appView.options().isDesugaredLibraryCompilation()) {
- EmulatedInterfaceProcessor.filterEmulatedInterfaceSubInterfaces(appView, builder);
+ new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
}
processCovariantReturnTypeAnnotations(builder);
- generateDesugaredLibraryAPIWrappers(builder, executor);
timing.end();
@@ -456,8 +442,33 @@
appView.appInfo().getMainDexInfo()));
}
- private void convertClasses(ExecutorService executorService) throws ExecutionException {
- D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executorService);
+ private DexApplication commitPendingSyntheticItems(
+ AppView<AppInfo> appView, DexApplication application) {
+ if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+ appView.setAppInfo(
+ new AppInfo(
+ appView.appInfo().getSyntheticItems().commit(application),
+ appView.appInfo().getMainDexInfo()));
+ application = appView.appInfo().app();
+ }
+ return application;
+ }
+
+ private void postProcessingDesugaringForD8(
+ D8MethodProcessor methodProcessor, ExecutorService executorService)
+ throws ExecutionException {
+ D8CfPostProcessingDesugaringEventConsumer eventConsumer =
+ CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor);
+ InterfaceMethodProcessorFacade interfaceDesugaring =
+ instructionDesugaring.getInterfaceMethodPostProcessingDesugaring(ExcludeDexResources);
+ CfPostProcessingDesugaringCollection.create(
+ appView, interfaceDesugaring, instructionDesugaring.getRetargetingInfo())
+ .postProcessingDesugaring(eventConsumer, executorService);
+ eventConsumer.finalizeDesugaring();
+ }
+
+ private void convertClasses(D8MethodProcessor methodProcessor, ExecutorService executorService)
+ throws ExecutionException {
ClassConverterResult classConverterResult =
ClassConverter.create(appView, this, methodProcessor).convertClasses(executorService);
@@ -470,6 +481,9 @@
rewriteEnclosingLambdaMethodAttributes(
appView, classConverterResult.getForcefullyMovedLambdaMethods());
+
+ instructionDesugaring.withDesugaredLibraryAPIConverter(
+ DesugaredLibraryAPIConverter::generateTrackingWarnings);
}
public void desugarClassesForD8(
@@ -564,35 +578,14 @@
}
}
- private boolean needsIRConversion(ProgramMethod method) {
+ private boolean needsIRConversion() {
if (appView.enableWholeProgramOptimizations()) {
return true;
}
if (options.testing.forceIRForCfToCfDesugar) {
return true;
}
- if (options.isDesugaredLibraryCompilation()) {
- return true;
- }
- if (!options.cfToCfDesugar) {
- return true;
- }
- if (desugaredLibraryAPIConverter != null
- && desugaredLibraryAPIConverter.shouldRegisterCallback(method)) {
- return true;
- }
- if (method.getDefinition().getCode() instanceof SynthesizedCode) {
- // SynthesizedCode needs IR to generate the code.
- return true;
- } else {
- NeedsIRDesugarUseRegistry useRegistry =
- new NeedsIRDesugarUseRegistry(
- method,
- appView,
- desugaredLibraryAPIConverter);
- method.registerCodeReferences(useRegistry);
- return useRegistry.needsDesugaring();
- }
+ return !options.cfToCfDesugar;
}
private void checkPrefixMerging(ProgramMethod method) {
@@ -760,8 +753,7 @@
timing.end();
if (enumUnboxer != null) {
- // TODO(b/190098858): Uncomment when methods are synthesized on-the-fly.
- // enumUnboxer.unsetRewriter();
+ enumUnboxer.unsetRewriter();
}
// All the code that should be impacted by the lenses inserted between phase 1 and phase 2
@@ -794,15 +786,9 @@
printPhase("Interface method desugaring");
finalizeInterfaceMethodRewritingThroughIR(executorService);
- runInterfaceDesugaringProcessors(IncludeAllResources, executorService);
+ runInterfaceDesugaringProcessorsForR8(IncludeAllResources, executorService);
feedback.updateVisibleOptimizationInfo();
- printPhase("Utility classes synthesis");
- synthesizeEnumUnboxingUtilityMethods(executorService);
-
- printPhase("Desugared library API Conversion finalization");
- generateDesugaredLibraryAPIWrappers(builder, executorService);
-
if (serviceLoaderRewriter != null) {
processSynthesizedServiceLoaderMethods(
serviceLoaderRewriter.getServiceLoadMethods(), executorService);
@@ -966,14 +952,6 @@
removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
}
- private void generateDesugaredLibraryAPIWrappers(
- DexApplication.Builder<?> builder, ExecutorService executorService)
- throws ExecutionException {
- if (desugaredLibraryAPIConverter != null) {
- desugaredLibraryAPIConverter.finalizeWrappers(builder, this, executorService);
- }
- }
-
private void clearDexMethodCompilationState() {
appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
}
@@ -1055,16 +1033,6 @@
return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
}
- List<CodeOptimization> getOptimizationsForPrimaryIRProcessing() {
- // TODO(b/140766440): Remove unnecessary steps once all sub steps are converted.
- return ImmutableList.of(this::optimize);
- }
-
- List<CodeOptimization> getOptimizationsForPostIRProcessing() {
- // TODO(b/140766440): Remove unnecessary steps once all sub steps are converted.
- return ImmutableList.of(this::optimize);
- }
-
// TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
public Timing processDesugaredMethod(
ProgramMethod method,
@@ -1151,7 +1119,7 @@
options.testing.hookInIrConversion.run();
}
- if (!needsIRConversion(method) || options.skipIR) {
+ if (!needsIRConversion() || options.skipIR) {
feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
return Timing.empty();
}
@@ -1509,10 +1477,9 @@
previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
- // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
if (desugaredLibraryAPIConverter != null
- && (!appView.enableWholeProgramOptimizations()
- || methodProcessor.isPrimaryMethodProcessor())) {
+ && appView.enableWholeProgramOptimizations()
+ && methodProcessor.isPrimaryMethodProcessor()) {
timing.begin("Desugar library API");
desugaredLibraryAPIConverter.desugar(code);
timing.end();
@@ -1664,7 +1631,8 @@
timing.end();
}
- if (appView.appInfo().withLiveness().isPinned(code.method().getReference())) {
+ if (appView.appInfo().withLiveness().isPinned(code.context().getReference())
+ || !appView.options().isOptimizing()) {
return;
}
@@ -1691,7 +1659,13 @@
.recordStaticValues(method.getHolder(), staticFieldValues);
}
methodOptimizationInfoCollector.collectMethodOptimizationInfo(
- method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos, timing);
+ method,
+ code,
+ feedback,
+ dynamicTypeOptimization,
+ instanceFieldInitializationInfos,
+ methodProcessor,
+ timing);
}
public void removeDeadCodeAndFinalizeIR(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 0cf836d..837d5fc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -681,7 +681,7 @@
new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
}
assert code.isConsistentSSABeforeTypesAreCorrect();
- assert code.hasNoVerticallyMergedClasses(appView);
+ assert code.hasNoMergedClasses(appView);
}
private DexField rewriteFieldReference(FieldLookupResult lookup, ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 9dfe2e3..13b7140 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -62,6 +63,9 @@
void setClassInlinerMethodConstraint(
ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint);
+ void setEnumUnboxerMethodClassification(
+ ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification);
+
void setInstanceInitializerInfoCollection(
DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
deleted file mode 100644
index 0d1c587..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ /dev/null
@@ -1,108 +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.ir.conversion;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-
-class NeedsIRDesugarUseRegistry extends UseRegistry {
-
- private boolean needsDesugaring = false;
- private final ProgramMethod context;
- private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
- public NeedsIRDesugarUseRegistry(
- ProgramMethod method,
- AppView<?> appView,
- DesugaredLibraryAPIConverter desugaredLibraryAPIConverter) {
- super(appView.dexItemFactory());
- this.context = method;
- this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
- }
-
- public boolean needsDesugaring() {
- return needsDesugaring;
- }
-
- @Override
- public void registerInitClass(DexType type) {
- if (!needsDesugaring
- && desugaredLibraryAPIConverter != null
- && desugaredLibraryAPIConverter.canConvert(type)) {
- needsDesugaring = true;
- }
- }
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {
- registerDesugaredLibraryAPIConverter(method);
- }
-
- @Override
- public void registerInvokeDirect(DexMethod method) {
- registerDesugaredLibraryAPIConverter(method);
- }
-
- private void registerDesugaredLibraryAPIConverter(DexMethod method) {
- if (!needsDesugaring) {
- needsDesugaring =
- desugaredLibraryAPIConverter != null
- && desugaredLibraryAPIConverter.shouldRewriteInvoke(method);
- }
- }
-
- @Override
- public void registerInvokeStatic(DexMethod method) {
- registerDesugaredLibraryAPIConverter(method);
- }
-
- @Override
- public void registerInvokeInterface(DexMethod method) {
- registerDesugaredLibraryAPIConverter(method);
- }
-
- @Override
- public void registerInvokeStatic(DexMethod method, boolean itf) {
- registerInvokeStatic(method);
- }
-
- @Override
- public void registerCallSite(DexCallSite callSite) {
- super.registerCallSite(callSite);
- needsDesugaring = true;
- }
-
- @Override
- public void registerInvokeSuper(DexMethod method) {
- registerDesugaredLibraryAPIConverter(method);
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {}
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {}
-
- @Override
- public void registerNewInstance(DexType type) {}
-
- @Override
- public void registerStaticFieldRead(DexField field) {}
-
- @Override
- public void registerStaticFieldWrite(DexField field) {}
-
- @Override
- public void registerTypeReference(DexType type) {}
-
- @Override
- public void registerInstanceOf(DexType type) {}
-}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index e40cae1..1ca91a7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -25,6 +25,10 @@
this.wave = wave;
}
+ public static Builder builder(ProcessorContext processorContext) {
+ return new Builder(processorContext);
+ }
+
public static OneTimeMethodProcessor create(ProgramMethod methodToProcess, AppView<?> appView) {
return create(SortedProgramMethodSet.create(methodToProcess), appView);
}
@@ -78,4 +82,23 @@
prepareForWaveExtensionProcessing();
}
}
+
+ public static class Builder {
+
+ private final SortedProgramMethodSet methodsToProcess = SortedProgramMethodSet.create();
+ private final ProcessorContext processorContext;
+
+ Builder(ProcessorContext processorContext) {
+ this.processorContext = processorContext;
+ }
+
+ public Builder add(ProgramMethod methodToProcess) {
+ methodsToProcess.add(methodToProcess);
+ return this;
+ }
+
+ public OneTimeMethodProcessor build() {
+ return create(methodsToProcess, processorContext);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 2d84083..cd9a9ec 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -7,9 +7,13 @@
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
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.itf.InterfaceMethodRewriter.Flavor;
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
/**
* Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -56,5 +60,11 @@
public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
+ public abstract InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(
+ Flavor flavor);
+
public abstract RetargetingInfo getRetargetingInfo();
+
+ public abstract void withDesugaredLibraryAPIConverter(
+ Consumer<DesugaredLibraryAPIConverter> consumer);
}
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 f05c16b..cb9aeb9 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
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.conversion.ClassConverterResult;
import com.android.tools.r8.ir.conversion.D8MethodProcessor;
import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
@@ -25,6 +26,7 @@
import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceDesugaringEventConsumer;
import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
@@ -33,6 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -48,7 +51,8 @@
RecordDesugaringEventConsumer,
TwrCloseResourceDesugaringEventConsumer,
InterfaceMethodDesugaringEventConsumer,
- DesugaredLibraryRetargeterInstructionEventConsumer {
+ DesugaredLibraryRetargeterInstructionEventConsumer,
+ DesugaredLibraryAPIConverterEventConsumer {
public static D8CfInstructionDesugaringEventConsumer createForD8(
D8MethodProcessor methodProcessor) {
@@ -68,6 +72,26 @@
return new CfInstructionDesugaringEventConsumer() {
@Override
+ public void acceptWrapperProgramClass(DexProgramClass clazz) {
+ assert false;
+ }
+
+ @Override
+ public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+ assert false;
+ }
+
+ @Override
+ public void acceptAPIConversion(ProgramMethod method) {
+ assert false;
+ }
+
+ @Override
+ public void acceptSuperAPIConversion(ProgramMethod method) {
+ assert false;
+ }
+
+ @Override
public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
assert false;
}
@@ -147,6 +171,8 @@
private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
new LinkedHashMap<>();
+ private final Map<DexProgramClass, SortedProgramMethodSet> pendingSuperAPIConversions =
+ new ConcurrentHashMap<>();
private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
private D8CfInstructionDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
@@ -229,14 +255,48 @@
methodProcessor.scheduleDesugaredMethodForProcessing(method);
}
+ @Override
+ public void acceptWrapperProgramClass(DexProgramClass clazz) {
+ methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+ }
+
+ @Override
+ public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void acceptAPIConversion(ProgramMethod method) {
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ }
+
+ @Override
+ public void acceptSuperAPIConversion(ProgramMethod method) {
+ SortedProgramMethodSet superAPIConversions =
+ pendingSuperAPIConversions.computeIfAbsent(
+ method.getHolder(), ignored -> SortedProgramMethodSet.createConcurrent());
+ superAPIConversions.add(method);
+ }
+
public List<ProgramMethod> finalizeDesugaring(
AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
List<ProgramMethod> needsProcessing = new ArrayList<>();
finalizeInvokeSpecialDesugaring(appView, needsProcessing::add);
finalizeLambdaDesugaring(classConverterResultBuilder, needsProcessing::add);
+ finalizeSuperAPIConversionDesugaring(needsProcessing::add);
return needsProcessing;
}
+ private void finalizeSuperAPIConversionDesugaring(Consumer<ProgramMethod> needsProcessing) {
+ for (SortedProgramMethodSet superAPIConversions : pendingSuperAPIConversions.values()) {
+ for (ProgramMethod superAPIConversion : superAPIConversions) {
+ superAPIConversion.getHolder().addDirectMethod(superAPIConversion.getDefinition());
+ needsProcessing.accept(superAPIConversion);
+ }
+ }
+ pendingSuperAPIConversions.clear();
+ }
+
private void finalizeInvokeSpecialDesugaring(
AppView<?> appView, Consumer<ProgramMethod> needsProcessing) {
// Fixup the code of the new private methods have that been synthesized.
@@ -348,6 +408,30 @@
}
@Override
+ public void acceptWrapperProgramClass(DexProgramClass clazz) {
+ // TODO(b/189912077): There should be nothing to do.
+ assert false;
+ }
+
+ @Override
+ public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+ // TODO(b/189912077): Should be added to live non program types.
+ assert false;
+ }
+
+ @Override
+ public void acceptAPIConversion(ProgramMethod method) {
+ // TODO(b/189912077): There should be nothing to do.
+ assert false;
+ }
+
+ @Override
+ public void acceptSuperAPIConversion(ProgramMethod method) {
+ // TODO(b/189912077): Manage pending conversions.
+ assert false;
+ }
+
+ @Override
public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
// Intentionally empty. The backported 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.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
index 2a2de5f..38a0413 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
@@ -3,7 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
public interface CfPostProcessingDesugaring {
- void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer);
+ void postProcessingDesugaring(
+ CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+ throws ExecutionException;
}
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 f659ad8..367e0ca 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
@@ -4,17 +4,24 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterPostProcessor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
-import java.util.Collections;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
+import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
public abstract class CfPostProcessingDesugaringCollection {
public static CfPostProcessingDesugaringCollection create(
- AppView<?> appView, RetargetingInfo retargetingInfo) {
+ AppView<?> appView,
+ InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+ RetargetingInfo retargetingInfo) {
if (appView.options().desugarState.isOn()) {
- return NonEmptyCfPostProcessingDesugaringCollection.create(appView, retargetingInfo);
+ return NonEmptyCfPostProcessingDesugaringCollection.create(
+ appView, interfaceMethodProcessorFacade, retargetingInfo);
}
return empty();
}
@@ -24,7 +31,8 @@
}
public abstract void postProcessingDesugaring(
- CfPostProcessingDesugaringEventConsumer eventConsumer);
+ CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+ throws ExecutionException;
public static class NonEmptyCfPostProcessingDesugaringCollection
extends CfPostProcessingDesugaringCollection {
@@ -37,19 +45,37 @@
}
public static CfPostProcessingDesugaringCollection create(
- AppView<?> appView, RetargetingInfo retargetingInfo) {
- if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+ AppView<?> appView,
+ InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+ RetargetingInfo retargetingInfo) {
+ ArrayList<CfPostProcessingDesugaring> desugarings = new ArrayList<>();
+ if (!appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+ desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo));
+ }
+ if (interfaceMethodProcessorFacade != null) {
+ desugarings.add(interfaceMethodProcessorFacade);
+ }
+ DesugaredLibraryAPIConverter desugaredLibraryAPIConverter =
+ appView.rewritePrefix.isRewriting() && !appView.enableWholeProgramOptimizations()
+ ? new DesugaredLibraryAPIConverter(appView, null)
+ : null;
+ // At this point the desugaredLibraryAPIConverter is required to be last to generate
+ // call-backs on the forwarding methods.
+ if (desugaredLibraryAPIConverter != null) {
+ desugarings.add(desugaredLibraryAPIConverter);
+ }
+ if (desugarings.isEmpty()) {
return empty();
}
- return new NonEmptyCfPostProcessingDesugaringCollection(
- Collections.singletonList(
- new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo)));
+ return new NonEmptyCfPostProcessingDesugaringCollection(desugarings);
}
@Override
- public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+ public void postProcessingDesugaring(
+ CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+ throws ExecutionException {
for (CfPostProcessingDesugaring desugaring : desugarings) {
- desugaring.postProcessingDesugaring(eventConsumer);
+ desugaring.postProcessingDesugaring(eventConsumer, executorService);
}
}
}
@@ -67,7 +93,9 @@
}
@Override
- public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+ public void postProcessingDesugaring(
+ CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+ throws ExecutionException {
// Intentionally 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 ec4f197..f5af1f1 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
@@ -10,9 +10,12 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.D8MethodProcessor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
-import java.util.function.Consumer;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.concurrent.ExecutionException;
/**
* Specialized Event consumer for desugaring finalization. During finalization, it is not possible
@@ -20,40 +23,36 @@
* explicit calls must be done here.
*/
public abstract class CfPostProcessingDesugaringEventConsumer
- implements DesugaredLibraryRetargeterPostProcessingEventConsumer {
- protected DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
- protected CfPostProcessingDesugaringEventConsumer(AppView<?> appView) {
- this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
- }
+ implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
+ InterfaceProcessingDesugaringEventConsumer,
+ DesugaredLibraryAPIConverterPostProcessingEventConsumer {
public static D8CfPostProcessingDesugaringEventConsumer createForD8(
- D8MethodProcessor methodProcessor, AppView<?> appView) {
- return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, appView);
+ D8MethodProcessor methodProcessor) {
+ return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor);
}
public static R8PostProcessingDesugaringEventConsumer createForR8(
- AppView<?> appView, Consumer<ProgramMethod> methodConsumer, SyntheticAdditions additions) {
- return new R8PostProcessingDesugaringEventConsumer(appView, methodConsumer, additions);
+ AppView<?> appView, SyntheticAdditions additions) {
+ return new R8PostProcessingDesugaringEventConsumer(appView, additions);
}
- public void finalizeDesugaring() {
- desugaredLibraryAPIConverter.generateTrackingWarnings();
- }
+ public abstract void finalizeDesugaring() throws ExecutionException;
public static class D8CfPostProcessingDesugaringEventConsumer
extends CfPostProcessingDesugaringEventConsumer {
private final D8MethodProcessor methodProcessor;
+ // Methods cannot be processed directly because we cannot add method to classes while
+ // concurrently processing other methods.
+ private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
- private D8CfPostProcessingDesugaringEventConsumer(
- D8MethodProcessor methodProcessor, AppView<?> appView) {
- super(appView);
+ private D8CfPostProcessingDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
this.methodProcessor = methodProcessor;
}
@Override
public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
- methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+ methodsToReprocess.addAll(clazz.programMethods());
}
@Override
@@ -68,27 +67,55 @@
@Override
public void acceptForwardingMethod(ProgramMethod method) {
- methodProcessor.scheduleDesugaredMethodForProcessing(method);
- // TODO(b/189912077): Uncomment when API conversion is performed cf to cf in D8.
- // desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
+ methodsToReprocess.add(method);
+ }
+
+ @Override
+ public void acceptCompanionClassClinit(ProgramMethod method) {
+ methodsToReprocess.add(method);
+ }
+
+ @Override
+ public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
+ methodsToReprocess.add(method);
+ }
+
+ @Override
+ public void acceptAPIConversionCallback(ProgramMethod method) {
+ methodsToReprocess.add(method);
+ }
+
+ @Override
+ public void finalizeDesugaring() throws ExecutionException {
+ assert methodProcessor.verifyNoPendingMethodProcessing();
+ methodProcessor.newWave();
+ methodProcessor.scheduleDesugaredMethodsForProcessing(methodsToReprocess);
+ methodProcessor.awaitMethodProcessing();
}
}
public static class R8PostProcessingDesugaringEventConsumer
extends CfPostProcessingDesugaringEventConsumer {
- private final Consumer<ProgramMethod> methodConsumer;
private final SyntheticAdditions additions;
- protected R8PostProcessingDesugaringEventConsumer(
- AppView<?> appView, Consumer<ProgramMethod> methodConsumer, SyntheticAdditions additions) {
- super(appView);
- this.methodConsumer = methodConsumer;
+ // The desugaredLibraryAPIConverter is required here because call-backs need to be generated
+ // once forwarding methods are generated. We should be able to remove it once the interface
+ // method desugaring and the API converter are moved cf to cf in R8.
+ private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
+
+ R8PostProcessingDesugaringEventConsumer(AppView<?> appView, SyntheticAdditions additions) {
+ this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
this.additions = additions;
}
@Override
+ public void finalizeDesugaring() throws ExecutionException {
+ desugaredLibraryAPIConverter.generateTrackingWarnings();
+ }
+
+ @Override
public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
- clazz.programMethods().forEach(methodConsumer);
+ additions.addLiveMethods(clazz.programMethods());
}
@Override
@@ -103,11 +130,23 @@
@Override
public void acceptForwardingMethod(ProgramMethod method) {
- methodConsumer.accept(method);
- ProgramMethod callback = desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
- if (callback != null) {
- methodConsumer.accept(callback);
- }
+ additions.addLiveMethod(method);
+ desugaredLibraryAPIConverter.generateCallbackIfRequired(method, this);
+ }
+
+ @Override
+ public void acceptCompanionClassClinit(ProgramMethod method) {
+ assert false : "TODO(b/183998768): Support Interface processing in R8";
+ }
+
+ @Override
+ public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
+ assert false : "TODO(b/183998768): Support Interface processing in R8";
+ }
+
+ @Override
+ public void acceptAPIConversionCallback(ProgramMethod method) {
+ additions.addLiveMethod(method);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 6cd9be6..c78f6db 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -7,9 +7,13 @@
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
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.itf.InterfaceMethodRewriter.Flavor;
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
@@ -58,7 +62,17 @@
}
@Override
+ public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(Flavor flavor) {
+ return null;
+ }
+
+ @Override
public RetargetingInfo getRetargetingInfo() {
return null;
}
+
+ @Override
+ public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+ // Intentionally empty.
+ }
}
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 8437128..b1ea3a0 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
@@ -14,10 +14,13 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
@@ -33,6 +36,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Consumer;
public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
@@ -42,6 +46,8 @@
private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
private final RecordRewriter recordRewriter;
private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
+ private final InterfaceMethodRewriter interfaceMethodRewriter;
+ private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
this.appView = appView;
@@ -49,6 +55,8 @@
this.nestBasedAccessDesugaring = null;
this.recordRewriter = null;
this.desugaredLibraryRetargeter = null;
+ this.interfaceMethodRewriter = null;
+ this.desugaredLibraryAPIConverter = null;
return;
}
this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
@@ -70,9 +78,23 @@
// TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
if (appView.options().isInterfaceMethodDesugaringEnabled()
&& !appView.enableWholeProgramOptimizations()) {
- desugarings.add(
+ interfaceMethodRewriter =
new InterfaceMethodRewriter(
- appView, backportedMethodRewriter, desugaredLibraryRetargeter));
+ appView, backportedMethodRewriter, desugaredLibraryRetargeter);
+ desugarings.add(interfaceMethodRewriter);
+ } else {
+ interfaceMethodRewriter = null;
+ }
+ desugaredLibraryAPIConverter =
+ appView.rewritePrefix.isRewriting() && !appView.enableWholeProgramOptimizations()
+ ? new DesugaredLibraryAPIConverter(
+ appView,
+ interfaceMethodRewriter,
+ desugaredLibraryRetargeter,
+ backportedMethodRewriter)
+ : null;
+ if (desugaredLibraryAPIConverter != null) {
+ desugarings.add(desugaredLibraryAPIConverter);
}
desugarings.add(new LambdaInstructionDesugaring(appView));
desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
@@ -306,10 +328,24 @@
}
@Override
+ public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(Flavor flavor) {
+ return interfaceMethodRewriter != null
+ ? interfaceMethodRewriter.getPostProcessingDesugaring(flavor)
+ : null;
+ }
+
+ @Override
public RetargetingInfo getRetargetingInfo() {
if (desugaredLibraryRetargeter != null) {
return desugaredLibraryRetargeter.getRetargetingInfo();
}
return null;
}
+
+ @Override
+ public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+ if (desugaredLibraryAPIConverter != null) {
+ consumer.accept(desugaredLibraryAPIConverter);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
index 42db4ed..3fb9030 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
@@ -4,11 +4,15 @@
package com.android.tools.r8.ir.desugar.desugaredlibrary;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -16,6 +20,9 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+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.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -24,11 +31,22 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+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.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.OptionalBool;
@@ -37,15 +55,17 @@
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
+import org.jetbrains.annotations.Nullable;
+import org.objectweb.asm.Opcodes;
// I convert library calls with desugared parameters/return values so they can work normally.
// In the JSON of the desugared library, one can specify conversions between desugared and
@@ -61,22 +81,31 @@
// DesugarType is only a rewritten type (generated through rewriting of type).
// The type, from the library, may either be rewritten to the desugarType,
// or be a rewritten type (generated through rewriting of vivifiedType).
-public class DesugaredLibraryAPIConverter {
+public class DesugaredLibraryAPIConverter
+ implements CfInstructionDesugaring, CfPostProcessingDesugaring {
static final String VIVIFIED_PREFIX = "$-vivified-$.";
public static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
+ private static final String SUPER_CONVERSION_METHOD_PREFIX = "api$super$conversion$";
private final AppView<?> appView;
private final DexItemFactory factory;
// For debugging only, allows to assert that synthesized code in R8 have been synthesized in the
// Enqueuer and not during IR processing.
private final Mode mode;
+ // This is used to filter out double desugaring on backported methods.
+ private final BackportedMethodRewriter backportedMethodRewriter;
+ private final InterfaceMethodRewriter interfaceMethodRewriter;
+ private final DesugaredLibraryRetargeter retargeter;
+
private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new IdentityHashMap<>();
private final Map<DexProgramClass, List<DexEncodedMethod>> pendingCallBackMethods =
new IdentityHashMap<>();
private final Set<DexMethod> trackedCallBackAPIs;
private final Set<DexMethod> trackedAPIs;
+ private final MethodAccessFlags superAPIConversionMethodAccessFlags =
+ MethodAccessFlags.createPublicStaticSynthetic();
public enum Mode {
GENERATE_CALLBACKS_AND_WRAPPERS,
@@ -84,9 +113,29 @@
}
public DesugaredLibraryAPIConverter(AppView<?> appView, Mode mode) {
+ this(appView, mode, null, null, null);
+ }
+
+ public DesugaredLibraryAPIConverter(
+ AppView<?> appView,
+ InterfaceMethodRewriter interfaceMethodRewriter,
+ DesugaredLibraryRetargeter retargeter,
+ BackportedMethodRewriter backportedMethodRewriter) {
+ this(appView, null, interfaceMethodRewriter, retargeter, backportedMethodRewriter);
+ }
+
+ private DesugaredLibraryAPIConverter(
+ AppView<?> appView,
+ Mode mode,
+ InterfaceMethodRewriter interfaceMethodRewriter,
+ DesugaredLibraryRetargeter retargeter,
+ BackportedMethodRewriter backportedMethodRewriter) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.mode = mode;
+ this.interfaceMethodRewriter = interfaceMethodRewriter;
+ this.retargeter = retargeter;
+ this.backportedMethodRewriter = backportedMethodRewriter;
this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView, this);
if (appView.options().testing.trackDesugaredAPIConversions) {
trackedCallBackAPIs = Sets.newConcurrentHashSet();
@@ -97,6 +146,95 @@
}
}
+ // TODO(b/191656218): Consider parallelizing post processing across classes instead of per
+ // implementor
+ // method.
+ @Override
+ public void postProcessingDesugaring(
+ CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService) {
+ assert noPendingWrappersOrConversions();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (!appView.isAlreadyLibraryDesugared(clazz)) {
+ ArrayList<DexEncodedMethod> callbacks = new ArrayList<>();
+ for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
+ if (shouldRegisterCallback(virtualProgramMethod)) {
+ if (trackedCallBackAPIs != null) {
+ trackedCallBackAPIs.add(virtualProgramMethod.getReference());
+ }
+ ProgramMethod callback =
+ generateCallbackMethod(
+ virtualProgramMethod.getDefinition(),
+ virtualProgramMethod.getHolder(),
+ eventConsumer);
+ callbacks.add(callback.getDefinition());
+ }
+ }
+ if (!callbacks.isEmpty()) {
+ clazz.addVirtualMethods(callbacks);
+ }
+ }
+ }
+ assert noPendingWrappersOrConversions();
+ generateTrackingWarnings();
+ }
+
+ private boolean noPendingWrappersOrConversions() {
+ for (DexProgramClass pendingSyntheticClass :
+ appView.getSyntheticItems().getPendingSyntheticClasses()) {
+ assert !isAPIConversionSyntheticType(pendingSyntheticClass.type);
+ }
+ return true;
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ DexItemFactory dexItemFactory) {
+ assert !appView.enableWholeProgramOptimizations();
+ if (needsDesugaring(instruction, context)) {
+ assert instruction.isInvoke();
+ return Collections.singletonList(
+ rewriteLibraryInvoke(
+ instruction.asInvoke(), methodProcessingContext, eventConsumer, context));
+ }
+ return null;
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ if (!instruction.isInvoke()) {
+ return false;
+ }
+ if (skipDesugaring(context)) {
+ return false;
+ }
+ CfInvoke invoke = instruction.asInvoke();
+ return shouldRewriteInvoke(
+ invoke.getMethod(), invoke.getInvokeType(context), invoke.isInterface(), context);
+ }
+
+ // We should not generate conversion for Wrappers and for conversion methods.
+ private boolean skipDesugaring(ProgramMethod method) {
+ return isAPIConversionSyntheticType(method.getHolderType())
+ || isSuperAPIConversionMethod(method);
+ }
+
+ private boolean isSuperAPIConversionMethod(ProgramMethod method) {
+ return method.getDefinition().isD8R8Synthesized()
+ && method.getAccessFlags().equals(superAPIConversionMethodAccessFlags)
+ && method.getName().toString().startsWith(SUPER_CONVERSION_METHOD_PREFIX);
+ }
+
+ private boolean isAPIConversionSyntheticType(DexType type) {
+ return wrapperSynthesizor.isSyntheticWrapper(type)
+ || appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.API_CONVERSION);
+ }
+
public static boolean isVivifiedType(DexType type) {
return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
}
@@ -107,6 +245,8 @@
public void desugar(IRCode code) {
+ assert appView.enableWholeProgramOptimizations();
+
if (wrapperSynthesizor.isSyntheticWrapper(code.method().getHolderType())) {
return;
}
@@ -130,13 +270,31 @@
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
// Library methods do not understand desugared types, hence desugared types have to be
// converted around non desugared library calls for the invoke to resolve.
- if (shouldRewriteInvoke(invokedMethod)) {
+ if (invokedMethod != null
+ && shouldRewriteInvoke(
+ invokedMethod,
+ invokeMethod.getType(),
+ invokeMethod.getInterfaceBit(),
+ code.context())) {
rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
}
}
}
}
+ @Nullable
+ private DexMethod getMethodForDesugaring(
+ DexMethod invokedMethod, boolean isInvokeSuper, ProgramMethod context) {
+ if (isInvokeSuper) {
+ // TODO(b/191656218): Use lookupInvokeSpecial instead when this is all to Cf.
+ DexClassAndMethod result =
+ appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+ return result != null ? result.getReference() : null;
+ }
+ // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
+ return invokedMethod;
+ }
+
private boolean validateCallbackWasGeneratedInEnqueuer(ProgramMethod method) {
if (!shouldRegisterCallback(method)) {
return true;
@@ -146,7 +304,17 @@
return true;
}
- public boolean shouldRewriteInvoke(DexMethod invokedMethod) {
+ private boolean shouldRewriteInvoke(
+ DexMethod unresolvedInvokedMethod,
+ Type invokeType,
+ Boolean isInterface,
+ ProgramMethod context) {
+ DexMethod invokedMethod =
+ getMethodForDesugaring(unresolvedInvokedMethod, invokeType == Type.SUPER, context);
+ if (invokedMethod == null) {
+ // Implies a resolution/look-up failure, we do not convert to keep the runtime error.
+ return false;
+ }
if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
|| invokedMethod.holder.isArrayType()) {
return false;
@@ -155,6 +323,19 @@
if (dexClass == null || !dexClass.isLibraryClass()) {
return false;
}
+ if (interfaceMethodRewriter != null
+ && interfaceMethodRewriter.needsRewriting(invokedMethod, invokeType, context)) {
+ return false;
+ }
+ assert retargeter == null || isInterface != null;
+ if (retargeter != null
+ && retargeter.hasNewInvokeTarget(invokedMethod, false, invokeType == Type.SUPER, context)) {
+ return false;
+ }
+ if (backportedMethodRewriter != null
+ && backportedMethodRewriter.methodIsBackport(invokedMethod)) {
+ return false;
+ }
return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView);
}
@@ -164,16 +345,18 @@
}
}
- public ProgramMethod generateCallbackIfRequired(ProgramMethod method) {
+ public void generateCallbackIfRequired(
+ ProgramMethod method, DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
if (!shouldRegisterCallback(method)) {
- return null;
+ return;
}
if (trackedCallBackAPIs != null) {
trackedCallBackAPIs.add(method.getReference());
}
- ProgramMethod callback = generateCallbackMethod(method.getDefinition(), method.getHolder());
- method.getHolder().addVirtualMethod(callback.getDefinition());
- return callback;
+ ProgramMethod callback =
+ generateCallbackMethod(method.getDefinition(), method.getHolder(), eventConsumer);
+ callback.getHolder().addVirtualMethod(callback.getDefinition());
+ assert noPendingWrappersOrConversions();
}
public boolean shouldRegisterCallback(ProgramMethod method) {
@@ -187,6 +370,7 @@
DexEncodedMethod definition = method.getDefinition();
if (definition.isPrivateMethod()
|| definition.isStatic()
+ || definition.isAbstract()
|| definition.isLibraryMethodOverride().isFalse()) {
return false;
}
@@ -311,19 +495,9 @@
return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
}
- public void finalizeWrappers(
- DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
- throws ExecutionException {
- // In D8, we generate the wrappers here. In R8, wrappers have already been generated in the
- // enqueuer, so nothing needs to be done.
- if (appView.enableWholeProgramOptimizations()) {
- return;
- }
- SortedProgramMethodSet callbacks = generateCallbackMethods();
- irConverter.processMethodsConcurrently(callbacks, executorService);
- if (appView.options().isDesugaredLibraryCompilation()) {
- wrapperSynthesizor.finalizeWrappersForL8();
- }
+ public void ensureWrappersForL8(CfInstructionDesugaringEventConsumer eventConsumer) {
+ assert appView.options().isDesugaredLibraryCompilation();
+ wrapperSynthesizor.ensureWrappersForL8(eventConsumer);
}
public SortedProgramMethodSet generateCallbackMethods() {
@@ -334,7 +508,7 @@
List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
callbacks.forEach(
callback -> {
- ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz);
+ ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz, null);
newVirtualMethods.add(callbackMethod.getDefinition());
allCallbackMethods.add(callbackMethod);
});
@@ -358,12 +532,14 @@
}
private ProgramMethod generateCallbackMethod(
- DexEncodedMethod originalMethod, DexProgramClass clazz) {
+ DexEncodedMethod originalMethod,
+ DexProgramClass clazz,
+ DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
DexMethod methodToInstall =
methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
CfCode cfCode =
new APIConverterWrapperCfCodeProvider(
- appView, originalMethod.getReference(), null, this, clazz.isInterface())
+ appView, originalMethod.getReference(), null, this, clazz.isInterface(), null)
.generateCfCode();
DexEncodedMethod newMethod =
wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
@@ -371,7 +547,13 @@
if (originalMethod.isLibraryMethodOverride().isTrue()) {
newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
}
- return new ProgramMethod(clazz, newMethod);
+ ProgramMethod callback = new ProgramMethod(clazz, newMethod);
+ if (eventConsumer != null) {
+ eventConsumer.acceptAPIConversionCallback(callback);
+ } else {
+ assert appView.enableWholeProgramOptimizations();
+ }
+ return callback;
}
private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
@@ -414,8 +596,10 @@
return vivifiedType;
}
- public void registerWrappersForLibraryInvokeIfRequired(DexMethod invokedMethod) {
- if (!shouldRewriteInvoke(invokedMethod)) {
+ public void registerWrappersForLibraryInvokeIfRequired(
+ DexMethod invokedMethod, Type invokeType, ProgramMethod context) {
+ // TODO(b/191656218): Once R8 support is done, use an unboxed boolean here.
+ if (!shouldRewriteInvoke(invokedMethod, invokeType, null, context)) {
return;
}
if (trackedAPIs != null) {
@@ -432,6 +616,120 @@
}
}
+ private DexMethod computeReturnConversion(
+ DexMethod invokedMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+ DexType returnType = invokedMethod.proto.returnType;
+ if (!appView.rewritePrefix.hasRewrittenType(returnType, appView)) {
+ return null;
+ }
+ if (canConvert(returnType)) {
+ DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView);
+ return ensureConversionMethod(returnType, newReturnType, returnType, eventConsumer);
+ }
+ reportInvalidInvoke(returnType, invokedMethod, "return ");
+ return null;
+ }
+
+ private DexMethod[] computeParameterConversions(
+ DexMethod invokedMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+ DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
+ DexType[] parameters = invokedMethod.proto.parameters.values;
+ for (int i = 0; i < parameters.length; i++) {
+ DexType argType = parameters[i];
+ if (appView.rewritePrefix.hasRewrittenType(argType, appView)) {
+ if (canConvert(argType)) {
+ DexType argVivifiedType = vivifiedTypeFor(argType, appView);
+ parameterConversions[i] =
+ ensureConversionMethod(argType, argType, argVivifiedType, eventConsumer);
+ } else {
+ reportInvalidInvoke(argType, invokedMethod, "parameter ");
+ }
+ }
+ }
+ return parameterConversions;
+ }
+
+ private CfInvoke rewriteLibraryInvoke(
+ CfInvoke invoke,
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context) {
+ DexMethod invokedMethod = invoke.getMethod();
+ if (trackedAPIs != null) {
+ trackedAPIs.add(invokedMethod);
+ }
+ DexProto newProto =
+ invoke.isInvokeStatic()
+ ? invokedMethod.proto
+ : factory.prependTypeToProto(invokedMethod.getHolderType(), invokedMethod.getProto());
+ DexMethod apiConversionMethod =
+ invoke.isInvokeSuper(context.getHolderType())
+ ? createSuperAPIConversion(
+ invoke, methodProcessingContext, eventConsumer, newProto, context)
+ : createOutlinedAPIConversion(invoke, methodProcessingContext, eventConsumer, newProto);
+ return new CfInvoke(Opcodes.INVOKESTATIC, apiConversionMethod, false);
+ }
+
+ private DexMethod createSuperAPIConversion(
+ CfInvoke invoke,
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ DexProto newProto,
+ ProgramMethod context) {
+ DexMethod invokedMethod = invoke.getMethod();
+ String uniqueSuffix = methodProcessingContext.createUniqueContext().getSyntheticSuffix();
+ DexMethod method =
+ factory.createMethod(
+ context.getHolderType(), newProto, SUPER_CONVERSION_METHOD_PREFIX + uniqueSuffix);
+ DexEncodedMethod apiConversion =
+ new DexEncodedMethod(
+ method,
+ superAPIConversionMethodAccessFlags,
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ new APIConversionCfCodeProvider(
+ appView,
+ method.holder,
+ invoke,
+ computeReturnConversion(invokedMethod, eventConsumer),
+ computeParameterConversions(invokedMethod, eventConsumer))
+ .generateCfCode(),
+ true);
+ eventConsumer.acceptSuperAPIConversion(new ProgramMethod(context.getHolder(), apiConversion));
+ return method;
+ }
+
+ private DexMethod createOutlinedAPIConversion(
+ CfInvoke invoke,
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ DexProto newProto) {
+ DexMethod invokedMethod = invoke.getMethod();
+ ProgramMethod outline =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ SyntheticKind.API_CONVERSION,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ .setProto(newProto)
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(
+ methodSig ->
+ new APIConversionCfCodeProvider(
+ appView,
+ methodSig.holder,
+ invoke,
+ computeReturnConversion(invokedMethod, eventConsumer),
+ computeParameterConversions(invokedMethod, eventConsumer))
+ .generateCfCode()));
+ eventConsumer.acceptAPIConversion(outline);
+ return outline.getReference();
+ }
+
private void rewriteLibraryInvoke(
IRCode code,
InvokeMethod invokeMethod,
@@ -563,7 +861,7 @@
private Instruction createParameterConversion(
IRCode code, DexType argType, DexType argVivifiedType, Value inValue) {
- DexMethod conversionMethod = ensureConversionMethod(argType, argType, argVivifiedType);
+ DexMethod conversionMethod = ensureConversionMethod(argType, argType, argVivifiedType, null);
// The value is null only if the input is null.
Value convertedValue =
createConversionValue(code, inValue.getType().nullability(), argVivifiedType, null);
@@ -572,7 +870,8 @@
private Instruction createReturnConversionAndReplaceUses(
IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
- DexMethod conversionMethod = ensureConversionMethod(returnType, returnVivifiedType, returnType);
+ DexMethod conversionMethod =
+ ensureConversionMethod(returnType, returnVivifiedType, returnType, null);
Value outValue = invokeMethod.outValue();
Value convertedValue =
createConversionValue(code, Nullability.maybeNull(), returnType, outValue.getLocalInfo());
@@ -589,7 +888,11 @@
}
}
- public DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
+ public DexMethod ensureConversionMethod(
+ DexType type,
+ DexType srcType,
+ DexType destType,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
// ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
// But everything is going to be rewritten, so we need to use vivifiedType and type".
DexType conversionHolder =
@@ -597,8 +900,8 @@
if (conversionHolder == null) {
conversionHolder =
type == srcType
- ? wrapperSynthesizor.ensureTypeWrapper(type)
- : wrapperSynthesizor.ensureVivifiedTypeWrapper(type);
+ ? wrapperSynthesizor.ensureTypeWrapper(type, eventConsumer)
+ : wrapperSynthesizor.ensureVivifiedTypeWrapper(type, eventConsumer);
}
assert conversionHolder != null;
return factory.createMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
new file mode 100644
index 0000000..65f5ece
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
+
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface DesugaredLibraryAPIConverterEventConsumer {
+
+ void acceptWrapperProgramClass(DexProgramClass clazz);
+
+ void acceptWrapperClasspathClass(DexClasspathClass clazz);
+
+ void acceptAPIConversion(ProgramMethod method);
+
+ void acceptSuperAPIConversion(ProgramMethod method);
+
+ interface DesugaredLibraryAPIConverterPostProcessingEventConsumer {
+
+ void acceptAPIConversionCallback(ProgramMethod method);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
index cf81745..84e3558 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
@@ -37,23 +38,6 @@
public class DesugaredLibraryConfiguration {
public static final String FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX = "j$/";
public static final boolean FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY = true;
- public static final DesugaredLibraryConfiguration EMPTY_DESUGARED_LIBRARY_CONFIGURATION =
- new DesugaredLibraryConfiguration(
- AndroidApiLevel.B,
- false,
- FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
- null,
- null,
- FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
- ImmutableMap.of(),
- ImmutableMap.of(),
- ImmutableMap.of(),
- ImmutableMap.of(),
- ImmutableMap.of(),
- ImmutableSet.of(),
- ImmutableList.of(),
- ImmutableList.of(),
- PrefixRewritingMapper.empty());
private final AndroidApiLevel requiredCompilationAPILevel;
private final boolean libraryCompilation;
private final String synthesizedLibraryClassesPackagePrefix;
@@ -102,7 +86,33 @@
}
public static DesugaredLibraryConfiguration empty() {
- return EMPTY_DESUGARED_LIBRARY_CONFIGURATION;
+ return new DesugaredLibraryConfiguration(
+ AndroidApiLevel.B,
+ false,
+ FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
+ null,
+ null,
+ FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableSet.of(),
+ ImmutableList.of(),
+ ImmutableList.of(),
+ PrefixRewritingMapper.empty()) {
+
+ @Override
+ public boolean isSupported(DexReference reference, AppView<?> appView) {
+ return false;
+ }
+
+ @Override
+ public boolean isEmptyConfiguration() {
+ return true;
+ }
+ };
}
private DesugaredLibraryConfiguration(
@@ -181,8 +191,8 @@
return emulateLibraryInterface;
}
- public boolean isSupported(DexMethod method, AppView<?> appView) {
- return prefixRewritingMapper.hasRewrittenType(method.getHolderType(), appView);
+ public boolean isSupported(DexReference reference, AppView<?> appView) {
+ return prefixRewritingMapper.hasRewrittenType(reference.getContextType(), appView);
}
// If the method is retargeted, answers the retargeted method, else null.
@@ -231,6 +241,10 @@
return jsonSource;
}
+ public boolean isEmptyConfiguration() {
+ return false;
+ }
+
public static class Builder {
private final DexItemFactory factory;
private final Reporter reporter;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
index 0b0039e..dac3079 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
@@ -23,6 +23,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
// The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
// for inserting interfaces on library boundaries and forwarding methods in the program, and to
@@ -41,7 +43,9 @@
}
@Override
- public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+ public void postProcessingDesugaring(
+ CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+ throws ExecutionException {
if (appView.options().isDesugaredLibraryCompilation()) {
ensureEmulatedDispatchMethodsSynthesized(eventConsumer);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
index 1f8322f..02eb59b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -150,6 +150,7 @@
.setCode(
methodSig ->
new EmulateInterfaceSyntheticCfCodeProvider(
+ methodSig.getHolderType(),
emulatedDispatchMethod.getHolderType(),
desugarMethod,
itfMethod,
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 6c1e889..7e2164f 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
@@ -117,14 +117,16 @@
return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
}
- DexType ensureTypeWrapper(DexType type) {
- return ensureWrappers(type).getWrapper().type;
+ DexType ensureTypeWrapper(DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+ return ensureWrappers(type, eventConsumer).getWrapper().type;
}
- DexType ensureVivifiedTypeWrapper(DexType type) {
- return ensureWrappers(type).getVivifiedWrapper().type;
+ DexType ensureVivifiedTypeWrapper(
+ DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+ return ensureWrappers(type, eventConsumer).getVivifiedWrapper().type;
}
+
public void registerWrapper(DexType type) {
wrappersToGenerate.add(type);
assert getValidClassToWrap(type) != null;
@@ -161,13 +163,17 @@
}
}
- private Wrappers ensureWrappers(DexType type) {
+ private Wrappers ensureWrappers(
+ DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
assert canGenerateWrapper(type) : type;
DexClass dexClass = getValidClassToWrap(type);
- return ensureWrappers(dexClass, ignored -> {});
+ return ensureWrappers(dexClass, ignored -> {}, eventConsumer);
}
- private Wrappers ensureWrappers(DexClass context, Consumer<DexClasspathClass> creationCallback) {
+ private Wrappers ensureWrappers(
+ DexClass context,
+ Consumer<DexClasspathClass> creationCallback,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
DexType type = context.type;
DexClass wrapper;
DexClass vivifiedWrapper;
@@ -180,15 +186,20 @@
vivifiedTypeFor(type),
type,
programContext,
- wrapperField -> synthesizeVirtualMethodsForTypeWrapper(programContext, wrapperField));
+ eventConsumer,
+ wrapperField ->
+ synthesizeVirtualMethodsForTypeWrapper(
+ programContext, wrapperField, eventConsumer));
vivifiedWrapper =
ensureProgramWrapper(
SyntheticKind.VIVIFIED_WRAPPER,
type,
vivifiedTypeFor(type),
programContext,
+ eventConsumer,
wrapperField ->
- synthesizeVirtualMethodsForVivifiedTypeWrapper(programContext, wrapperField));
+ synthesizeVirtualMethodsForVivifiedTypeWrapper(
+ programContext, wrapperField, eventConsumer));
DexField wrapperField = getWrapperUniqueField(wrapper);
DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
ensureProgramConversionMethod(
@@ -205,7 +216,9 @@
type,
classpathOrLibraryContext,
creationCallback,
- wrapperField -> synthesizeVirtualMethodsForTypeWrapper(context, wrapperField));
+ eventConsumer,
+ wrapperField ->
+ synthesizeVirtualMethodsForTypeWrapper(context, wrapperField, eventConsumer));
vivifiedWrapper =
ensureClasspathWrapper(
SyntheticKind.VIVIFIED_WRAPPER,
@@ -213,8 +226,10 @@
vivifiedTypeFor(type),
classpathOrLibraryContext,
creationCallback,
+ eventConsumer,
wrapperField ->
- synthesizeVirtualMethodsForVivifiedTypeWrapper(context, wrapperField));
+ synthesizeVirtualMethodsForVivifiedTypeWrapper(
+ context, wrapperField, eventConsumer));
DexField wrapperField = getWrapperUniqueField(wrapper);
DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
ensureClasspathConversionMethod(
@@ -242,6 +257,7 @@
DexType wrappingType,
DexType wrappedType,
DexProgramClass programContext,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer,
Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
return appView
.getSyntheticItems()
@@ -252,9 +268,13 @@
builder -> buildWrapper(wrappingType, wrappedType, programContext, builder),
// The creation of virtual methods may require new wrappers, this needs to happen
// once the wrapper is created to avoid infinite recursion.
- wrapper ->
- wrapper.setVirtualMethods(
- virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper))));
+ wrapper -> {
+ if (eventConsumer != null) {
+ eventConsumer.acceptWrapperProgramClass(wrapper);
+ }
+ wrapper.setVirtualMethods(
+ virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
+ });
}
private DexClasspathClass ensureClasspathWrapper(
@@ -263,6 +283,7 @@
DexType wrappedType,
ClasspathOrLibraryClass classpathOrLibraryContext,
Consumer<DexClasspathClass> creationCallback,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer,
Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
return appView
.getSyntheticItems()
@@ -276,6 +297,9 @@
// The creation of virtual methods may require new wrappers, this needs to happen
// once the wrapper is created to avoid infinite recursion.
wrapper -> {
+ if (eventConsumer != null) {
+ eventConsumer.acceptWrapperClasspathClass(wrapper);
+ }
wrapper.setVirtualMethods(
virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
creationCallback.accept(wrapper);
@@ -382,7 +406,9 @@
}
private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
- DexClass dexClass, DexEncodedField wrapperField) {
+ DexClass dexClass,
+ DexEncodedField wrapperField,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only types in their signature, but each method the wrapper forwards
@@ -421,7 +447,12 @@
} else {
cfCode =
new APIConverterVivifiedWrapperCfCodeProvider(
- appView, methodToInstall, wrapperField.getReference(), converter, isInterface)
+ appView,
+ methodToInstall,
+ wrapperField.getReference(),
+ converter,
+ isInterface,
+ eventConsumer)
.generateCfCode();
}
DexEncodedMethod newDexEncodedMethod =
@@ -432,7 +463,9 @@
}
private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
- DexClass dexClass, DexEncodedField wrapperField) {
+ DexClass dexClass,
+ DexEncodedField wrapperField,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only vivified types in their signature, but each method the wrapper
@@ -465,7 +498,8 @@
dexEncodedMethod.getReference(),
wrapperField.getReference(),
converter,
- isInterface)
+ isInterface,
+ eventConsumer)
.generateCfCode();
}
DexEncodedMethod newDexEncodedMethod =
@@ -574,7 +608,7 @@
field, fieldAccessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
}
- void finalizeWrappersForL8() {
+ void ensureWrappersForL8(DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
for (DexType type : conf.getWrapperConversions()) {
assert !conf.getCustomConversions().containsKey(type);
@@ -582,7 +616,7 @@
// In broken set-ups we can end up having a json files containing wrappers of non desugared
// classes. Such wrappers are not required since the class won't be rewritten.
if (validClassToWrap.isProgramClass()) {
- ensureWrappers(validClassToWrap, ignored -> {});
+ ensureWrappers(validClassToWrap, ignored -> {}, eventConsumer);
}
}
}
@@ -599,7 +633,8 @@
classpathWrapper -> {
changed.set(true);
synthesizedCallback.accept(classpathWrapper);
- });
+ },
+ null);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index d86d22c..14e5fea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -380,7 +380,8 @@
}
@Override
- public void process(DexProgramClass clazz, ProgramMethodSet synthesizedMethods) {
+ public void process(
+ DexProgramClass clazz, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
if (!clazz.isInterface()) {
visitClassInfo(clazz, new ReportingContext(clazz, clazz));
}
@@ -389,11 +390,11 @@
// We introduce forwarding methods only once all desugaring has been performed to avoid
// confusing the look-up with inserted forwarding methods.
@Override
- public final void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
+ public final void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
newSyntheticMethods.forEach(
(clazz, newForwardingMethods) -> {
clazz.addVirtualMethods(newForwardingMethods.toDefinitionSet());
- newForwardingMethods.forEach(synthesizedMethods::add);
+ newForwardingMethods.forEach(eventConsumer::acceptForwardingMethod);
});
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
new file mode 100644
index 0000000..b2308ae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -0,0 +1,158 @@
+// 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.itf;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+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.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.utils.IterableUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public final class EmulatedInterfaceApplicationRewriter {
+
+ private final AppView<?> appView;
+ private final Map<DexType, DexType> emulatedInterfaces;
+
+ public EmulatedInterfaceApplicationRewriter(AppView<?> appView) {
+ this.appView = appView;
+ this.emulatedInterfaces =
+ appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+ }
+
+ public void rewriteApplication(DexApplication.Builder<?> builder) {
+ assert appView.options().isDesugaredLibraryCompilation();
+ ArrayList<DexProgramClass> newProgramClasses = new ArrayList<>();
+ for (DexProgramClass clazz : builder.getProgramClasses()) {
+ if (emulatedInterfaces.containsKey(clazz.type)) {
+ newProgramClasses.add(rewriteEmulatedInterface(clazz));
+ } else if (clazz.isInterface()
+ && !appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
+ && isEmulatedInterfaceSubInterface(clazz)) {
+ // Intentionally filter such classes out.
+ handleEmulatedInterfaceSubInterface(clazz);
+ } else {
+ newProgramClasses.add(clazz);
+ }
+ }
+ builder.replaceProgramClasses(newProgramClasses);
+ }
+
+ private void handleEmulatedInterfaceSubInterface(DexProgramClass clazz) {
+ // TODO(b/183918843): Investigate how to specify these in the json file.
+ // These are interfaces which needs a companion class for desugared library to work, but
+ // the interface is not supported outside of desugared library. The interface has to be
+ // present during the compilation for the companion class to be generated, but filtered out
+ // afterwards. The companion class needs to be rewritten to have the desugared library
+ // prefix since all classes in desugared library should have the prefix, we used the
+ // questionable method convertJavaNameToDesugaredLibrary to generate a correct type.
+ String newName =
+ appView
+ .options()
+ .desugaredLibraryConfiguration
+ .convertJavaNameToDesugaredLibrary(clazz.type);
+ InterfaceMethodRewriter.addCompanionClassRewriteRule(clazz.type, newName, appView);
+ }
+
+ private boolean isEmulatedInterfaceSubInterface(DexClass subInterface) {
+ assert !emulatedInterfaces.containsKey(subInterface.type);
+ LinkedList<DexType> workList = new LinkedList<>(Arrays.asList(subInterface.interfaces.values));
+ while (!workList.isEmpty()) {
+ DexType next = workList.removeFirst();
+ if (emulatedInterfaces.containsKey(next)) {
+ return true;
+ }
+ DexClass nextClass = appView.definitionFor(next);
+ if (nextClass != null) {
+ workList.addAll(Arrays.asList(nextClass.interfaces.values));
+ }
+ }
+ return false;
+ }
+
+ // The method transforms emulated interface such as they now have the rewritten type and
+ // implement the rewritten version of each emulated interface they implement.
+ private DexProgramClass rewriteEmulatedInterface(DexProgramClass emulatedInterface) {
+ if (appView.isAlreadyLibraryDesugared(emulatedInterface)) {
+ return emulatedInterface;
+ }
+ DexType newType = emulatedInterfaces.get(emulatedInterface.type);
+ assert newType != null;
+ DexEncodedMethod[] newVirtualMethods =
+ renameHolder(emulatedInterface.virtualMethods(), newType);
+ DexEncodedMethod[] newDirectMethods = renameHolder(emulatedInterface.directMethods(), newType);
+ assert emulatedInterface.getSuperType() == appView.dexItemFactory().objectType;
+ assert !emulatedInterface.hasFields();
+ assert emulatedInterface.getNestHost() == null;
+ assert !emulatedInterface.hasNestMemberAttributes();
+ assert !emulatedInterface.hasFields();
+ DexProgramClass newEmulatedInterface =
+ new DexProgramClass(
+ newType,
+ emulatedInterface.getOriginKind(),
+ emulatedInterface.getOrigin(),
+ emulatedInterface.getAccessFlags(),
+ appView.dexItemFactory().objectType,
+ DexTypeList.empty(),
+ emulatedInterface.getSourceFile(),
+ null,
+ Collections.emptyList(),
+ null, // Note that we clear the enclosing and inner class attributes.
+ Collections.emptyList(),
+ emulatedInterface.getClassSignature(),
+ emulatedInterface.annotations(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ newDirectMethods,
+ newVirtualMethods,
+ false,
+ emulatedInterface.getChecksumSupplier());
+ newEmulatedInterface.addExtraInterfaces(
+ getRewrittenInterfacesOfEmulatedInterface(emulatedInterface));
+ return newEmulatedInterface;
+ }
+
+ private List<GenericSignature.ClassTypeSignature> getRewrittenInterfacesOfEmulatedInterface(
+ DexProgramClass emulatedInterface) {
+ List<GenericSignature.ClassTypeSignature> newInterfaces = new ArrayList<>();
+ ClassSignature classSignature = emulatedInterface.getClassSignature();
+ for (int i = 0; i < emulatedInterface.interfaces.size(); i++) {
+ DexType itf = emulatedInterface.interfaces.values[i];
+ if (emulatedInterfaces.containsKey(itf)) {
+ List<GenericSignature.FieldTypeSignature> typeArguments;
+ if (classSignature == null) {
+ typeArguments = Collections.emptyList();
+ } else {
+ GenericSignature.ClassTypeSignature classTypeSignature =
+ classSignature.superInterfaceSignatures().get(i);
+ assert itf == classTypeSignature.type();
+ typeArguments = classTypeSignature.typeArguments();
+ }
+ newInterfaces.add(
+ new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
+ }
+ }
+ return newInterfaces;
+ }
+
+ private DexEncodedMethod[] renameHolder(Iterable<DexEncodedMethod> methods, DexType newName) {
+ List<DexEncodedMethod> methodArray = IterableUtils.toNewArrayList(methods);
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[methodArray.size()];
+ for (int i = 0; i < newMethods.length; i++) {
+ newMethods[i] = methodArray.get(i).toRenamedHolderMethod(newName, appView.dexItemFactory());
+ }
+ return newMethods;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index b9e9889..f0d298f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -6,24 +6,19 @@
import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
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.utils.IterableUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -37,8 +32,6 @@
public final class EmulatedInterfaceProcessor implements InterfaceDesugaringProcessor {
- private static final String JUNK_SUFFIX = "$JUNK";
-
private final AppView<?> appView;
private final InterfaceMethodRewriter rewriter;
private final Map<DexType, DexType> emulatedInterfaces;
@@ -94,51 +87,8 @@
}
}
- // The method transforms emulated interface such as they implement the rewritten version
- // of each emulated interface they implement. Such change should have no effect on the look-up
- // results, since each class implementing an emulated interface should also implement the
- // rewritten one.
- private void replaceInterfacesInEmulatedInterface(DexProgramClass emulatedInterface) {
- List<GenericSignature.ClassTypeSignature> newInterfaces = new ArrayList<>();
- ClassSignature classSignature = emulatedInterface.getClassSignature();
- for (int i = 0; i < emulatedInterface.interfaces.size(); i++) {
- DexType itf = emulatedInterface.interfaces.values[i];
- if (emulatedInterfaces.containsKey(itf)) {
- List<GenericSignature.FieldTypeSignature> typeArguments;
- if (classSignature == null) {
- typeArguments = Collections.emptyList();
- } else {
- GenericSignature.ClassTypeSignature classTypeSignature =
- classSignature.superInterfaceSignatures().get(i);
- assert itf == classTypeSignature.type();
- typeArguments = classTypeSignature.typeArguments();
- }
- newInterfaces.add(
- new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
- }
- }
- emulatedInterface.replaceInterfaces(newInterfaces);
- }
-
- private void renameEmulatedInterface(DexProgramClass emulatedInterface) {
- DexType newType = emulatedInterfaces.get(emulatedInterface.type);
- assert newType != null;
- emulatedInterface.type = newType;
- emulatedInterface.setVirtualMethods(renameHolder(emulatedInterface.virtualMethods(), newType));
- emulatedInterface.setDirectMethods(renameHolder(emulatedInterface.directMethods(), newType));
- }
-
- private DexEncodedMethod[] renameHolder(Iterable<DexEncodedMethod> methods, DexType newName) {
- List<DexEncodedMethod> methods1 = IterableUtils.toNewArrayList(methods);
- DexEncodedMethod[] newMethods = new DexEncodedMethod[methods1.size()];
- for (int i = 0; i < newMethods.length; i++) {
- newMethods[i] = methods1.get(i).toRenamedHolderMethod(newName, appView.dexItemFactory());
- }
- return newMethods;
- }
-
DexProgramClass ensureEmulateInterfaceLibrary(
- DexProgramClass emulatedInterface, ProgramMethodSet synthesizedMethods) {
+ DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
assert rewriter.isEmulatedInterface(emulatedInterface.type);
DexProgramClass emulateInterfaceClass =
appView
@@ -156,7 +106,7 @@
synthesizeEmulatedInterfaceMethod(
method, emulatedInterface, methodBuilder))),
ignored -> {});
- emulateInterfaceClass.forEachProgramMethod(synthesizedMethods::add);
+ emulateInterfaceClass.forEachProgramMethod(eventConsumer::acceptEmulatedInterfaceMethod);
assert emulateInterfaceClass.getType()
== InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType(
emulatedInterface.type, appView.dexItemFactory());
@@ -180,9 +130,10 @@
.setProto(emulatedMethod.getProto())
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(
- theMethod ->
+ emulatedInterfaceMethod ->
new EmulateInterfaceSyntheticCfCodeProvider(
- theMethod.getHolderType(),
+ emulatedInterfaceMethod.getHolderType(),
+ method.getHolderType(),
companionMethod,
libraryMethod,
extraDispatchCases,
@@ -266,14 +217,15 @@
}
@Override
- public void process(DexProgramClass emulatedInterface, ProgramMethodSet synthesizedMethods) {
+ public void process(
+ DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
if (!appView.options().isDesugaredLibraryCompilation()
|| !rewriter.isEmulatedInterface(emulatedInterface.type)
|| appView.isAlreadyLibraryDesugared(emulatedInterface)) {
return;
}
if (needsEmulateInterfaceLibrary(emulatedInterface)) {
- ensureEmulateInterfaceLibrary(emulatedInterface, synthesizedMethods);
+ ensureEmulateInterfaceLibrary(emulatedInterface, eventConsumer);
}
}
@@ -282,74 +234,8 @@
}
@Override
- public void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
+ public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
warnMissingEmulatedInterfaces();
- if (!appView.options().isDesugaredLibraryCompilation()) {
- return;
- }
- for (DexType interfaceType : emulatedInterfaces.keySet()) {
- DexClass theInterface = appView.definitionFor(interfaceType);
- if (theInterface != null && theInterface.isProgramClass()) {
- DexProgramClass emulatedInterface = theInterface.asProgramClass();
- if (!appView.isAlreadyLibraryDesugared(emulatedInterface)) {
- replaceInterfacesInEmulatedInterface(emulatedInterface);
- renameEmulatedInterface(emulatedInterface);
- }
- }
- }
- }
-
- // TODO(b/183918843): Investigate what to do. The whole method is trying to fill a hole in the
- // desugaring library specifications by patching types and classes through questionable renaming.
- public static void filterEmulatedInterfaceSubInterfaces(
- AppView<?> appView, DexApplication.Builder<?> builder) {
- assert appView.options().isDesugaredLibraryCompilation();
- ArrayList<DexProgramClass> filteredProgramClasses = new ArrayList<>();
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (clazz.isInterface()
- && !appView
- .options()
- .desugaredLibraryConfiguration
- .getEmulateLibraryInterface()
- .containsKey(clazz.type)
- && !appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
- && isEmulatedInterfaceSubInterface(clazz, appView)) {
- String newName =
- appView
- .options()
- .desugaredLibraryConfiguration
- .convertJavaNameToDesugaredLibrary(clazz.type);
- InterfaceMethodRewriter.addCompanionClassRewriteRule(clazz.type, newName, appView);
- } else {
- filteredProgramClasses.add(clazz);
- }
- }
- builder.replaceProgramClasses(filteredProgramClasses);
- }
-
- private static boolean isEmulatedInterfaceSubInterface(
- DexClass subInterface, AppView<?> appView) {
- assert !appView
- .options()
- .desugaredLibraryConfiguration
- .getEmulateLibraryInterface()
- .containsKey(subInterface.type);
- LinkedList<DexType> workList = new LinkedList<>(Arrays.asList(subInterface.interfaces.values));
- while (!workList.isEmpty()) {
- DexType next = workList.removeFirst();
- if (appView
- .options()
- .desugaredLibraryConfiguration
- .getEmulateLibraryInterface()
- .containsKey(next)) {
- return true;
- }
- DexClass nextClass = appView.definitionFor(next);
- if (nextClass != null) {
- workList.addAll(Arrays.asList(nextClass.interfaces.values));
- }
- }
- return false;
}
private void warnMissingEmulatedInterfaces() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
index 7f3d047..4f4ba32 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.desugar.itf;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
public interface InterfaceDesugaringProcessor {
@@ -14,11 +13,11 @@
// so this phase cannot modify the classes themselves (for example insertion/removal of methods).
// The phase can insert new classes with new methods, such as emulated interface dispatch classes
// or companion classes with their methods.
- void process(DexProgramClass clazz, ProgramMethodSet synthesizedMethods);
+ void process(DexProgramClass clazz, InterfaceProcessingDesugaringEventConsumer eventConsumer);
// The finalization phase is done at a join point, after all code desugaring have been performed.
// All finalization phases of all desugaring processors are performed sequentially.
// Complex computations should be avoided if possible here and be moved to the concurrent phase.
// Classes may be mutated here (new methods can be inserted, etc.).
- void finalizeProcessing(ProgramMethodSet synthesizedMethods);
+ void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
index 731316f..71844b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -6,7 +6,10 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -16,21 +19,22 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-class InterfaceMethodProcessorFacade {
+public class InterfaceMethodProcessorFacade implements CfPostProcessingDesugaring {
private final AppView<?> appView;
+ private final Flavor flavour;
+ private final List<InterfaceDesugaringProcessor> interfaceDesugaringProcessors;
- InterfaceMethodProcessorFacade(AppView<?> appView) {
+ InterfaceMethodProcessorFacade(
+ AppView<?> appView, Flavor flavour, InterfaceMethodRewriter rewriter) {
this.appView = appView;
+ this.flavour = flavour;
+ interfaceDesugaringProcessors = instantiateInterfaceDesugaringProcessors(appView, rewriter);
}
- /** Runs the interfaceProcessor, the class processor and the emulated interface processor. */
- void runInterfaceDesugaringProcessors(
- InterfaceMethodRewriter rewriter,
- IRConverter converter,
- Flavor flavour,
- ExecutorService executorService)
- throws ExecutionException {
+ private List<InterfaceDesugaringProcessor> instantiateInterfaceDesugaringProcessors(
+ AppView<?> appView, InterfaceMethodRewriter rewriter) {
+
// During L8 compilation, emulated interfaces are processed to be renamed, to have
// their interfaces fixed-up and to generate the emulated dispatch code.
EmulatedInterfaceProcessor emulatedInterfaceProcessor =
@@ -46,17 +50,47 @@
// classes if needed.
InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView, rewriter);
- // The interface processors must be ordered so that finalization of the processing is performed
- // in that order. The emulatedInterfaceProcessor has to be last at this point to avoid renaming
- // emulated interfaces before the other processing.
- ImmutableList<InterfaceDesugaringProcessor> orderedInterfaceDesugaringProcessors =
- ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
+ // The processors can be listed in any order.
+ return ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
+ }
+
+ /** Runs the interfaceProcessor, the class processor and the emulated interface processor. */
+ void runInterfaceDesugaringProcessorsForR8(IRConverter converter, ExecutorService executorService)
+ throws ExecutionException {
+
+ CollectingInterfaceDesugaringEventConsumer eventConsumer =
+ new CollectingInterfaceDesugaringEventConsumer();
+ processClassesConcurrently(eventConsumer, executorService);
+ converter.processMethodsConcurrently(
+ eventConsumer.getSortedSynthesizedMethods(), executorService);
+ }
+
+ // This temporary class avoids the duality between collecting with IR processing and
+ // having events with the Cf desugaring.
+ private static class CollectingInterfaceDesugaringEventConsumer
+ implements InterfaceProcessingDesugaringEventConsumer {
SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.createConcurrent();
- processClassesConcurrently(
- orderedInterfaceDesugaringProcessors, sortedSynthesizedMethods, flavour, executorService);
- assert converter != null;
- converter.processMethodsConcurrently(sortedSynthesizedMethods, executorService);
+
+ @Override
+ public void acceptForwardingMethod(ProgramMethod method) {
+ sortedSynthesizedMethods.add(method);
+ }
+
+ @Override
+ public void acceptCompanionClassClinit(ProgramMethod method) {
+ sortedSynthesizedMethods.add(method);
+ }
+
+ @Override
+ public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
+
+ sortedSynthesizedMethods.add(method);
+ }
+
+ public SortedProgramMethodSet getSortedSynthesizedMethods() {
+ return sortedSynthesizedMethods;
+ }
}
private boolean shouldProcess(DexProgramClass clazz, Flavor flavour) {
@@ -67,22 +101,28 @@
}
private void processClassesConcurrently(
- List<InterfaceDesugaringProcessor> processors,
- SortedProgramMethodSet sortedSynthesizedMethods,
- Flavor flavour,
- ExecutorService executorService)
+ InterfaceProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processItems(
Iterables.filter(
appView.appInfo().classes(), (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
clazz -> {
- for (InterfaceDesugaringProcessor processor : processors) {
- processor.process(clazz, sortedSynthesizedMethods);
+ for (InterfaceDesugaringProcessor processor : interfaceDesugaringProcessors) {
+ processor.process(clazz, eventConsumer);
}
},
executorService);
- for (InterfaceDesugaringProcessor processor : processors) {
- processor.finalizeProcessing(sortedSynthesizedMethods);
+ for (InterfaceDesugaringProcessor processor : interfaceDesugaringProcessors) {
+ processor.finalizeProcessing(eventConsumer);
}
}
+
+ @Override
+ public void postProcessingDesugaring(
+ CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+ throws ExecutionException {
+ // TODO(b/183998768): Would be nice to use the ClassProcessing for the processing of classes,
+ // and do here only the finalization.
+ processClassesConcurrently(eventConsumer, executorService);
+ }
}
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 413f803..e6471fe 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
@@ -467,6 +467,7 @@
eventConsumer.acceptInvokeStaticInterfaceOutliningMethod(
staticOutliningMethod, context);
};
+ // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
return rewriteInvokeStatic(
invoke.getMethod(),
invoke.isInterface(),
@@ -671,6 +672,7 @@
methodProcessor,
methodProcessingContext);
if (instruction.isInvokeStatic()) {
+ // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
rewriteInvokeStatic(
invoke.getInvokedMethod(),
invoke.getInterfaceBit(),
@@ -1377,11 +1379,15 @@
this.synthesizedMethods.clear();
}
- public void runInterfaceDesugaringProcessors(
+ public void runInterfaceDesugaringProcessorsForR8(
IRConverter converter, Flavor flavour, ExecutorService executorService)
throws ExecutionException {
- new InterfaceMethodProcessorFacade(appView)
- .runInterfaceDesugaringProcessors(this, converter, flavour, executorService);
+ getPostProcessingDesugaring(flavour)
+ .runInterfaceDesugaringProcessorsForR8(converter, executorService);
+ }
+
+ public InterfaceMethodProcessorFacade getPostProcessingDesugaring(Flavor flavour) {
+ return new InterfaceMethodProcessorFacade(appView, flavour, this);
}
final boolean isDefaultMethod(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
new file mode 100644
index 0000000..2581364
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
@@ -0,0 +1,16 @@
+// 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.itf;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface InterfaceProcessingDesugaringEventConsumer {
+
+ void acceptForwardingMethod(ProgramMethod method);
+
+ void acceptCompanionClassClinit(ProgramMethod method);
+
+ void acceptEmulatedInterfaceMethod(ProgramMethod method);
+}
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 cb752ad..2dd65a9 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
@@ -47,7 +47,6 @@
import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.ImmutableList;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -80,12 +79,13 @@
}
@Override
- public void process(DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
+ public void process(
+ DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
if (!iface.isInterface()) {
return;
}
analyzeBridges(iface);
- ensureCompanionClassMethods(iface, synthesizedMethods);
+ ensureCompanionClassMethods(iface, eventConsumer);
}
private void analyzeBridges(DexProgramClass iface) {
@@ -99,8 +99,8 @@
}
private void ensureCompanionClassMethods(
- DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
- ensureCompanionClassInitializesInterface(iface, synthesizedMethods);
+ DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
+ ensureCompanionClassInitializesInterface(iface, eventConsumer);
// TODO(b/183998768): Once fixed, the methods should be added for processing.
// D8 and R8 don't need to optimize the methods since they are just moved from interfaces and
// don't need to be re-processed.
@@ -134,7 +134,7 @@
}
private void ensureCompanionClassInitializesInterface(
- DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
+ DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
if (!hasStaticMethodThatTriggersNonTrivialClassInitializer(iface)) {
return;
}
@@ -146,7 +146,7 @@
appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
appView,
methodBuilder -> createCompanionClassInitializer(iface, clinitField, methodBuilder));
- synthesizedMethods.add(clinit);
+ eventConsumer.acceptCompanionClassClinit(clinit);
}
private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
@@ -441,7 +441,7 @@
}
@Override
- public void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
+ public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
InterfaceProcessorNestedGraphLens graphLens = postProcessInterfaces();
if (appView.enableWholeProgramOptimizations() && graphLens != null) {
appView.setGraphLens(graphLens);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 0ecfc91..c6bb8a7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -119,9 +120,20 @@
InstructionListIterator iterator = block.listIterator(code, block.getInstructions().size());
while (iterator.hasPrevious()) {
Instruction current = iterator.previous();
- // Remove unused invoke results.
- if (current.isInvoke() && current.hasOutValue() && !current.outValue().isUsed()) {
- current.setOutValue(null);
+ if (current.hasOutValue()) {
+ // Replace unnecessary cast values.
+ if (current.isCheckCast()) {
+ CheckCast checkCast = current.asCheckCast();
+ if (!checkCast.isRefiningStaticType(appView.options())
+ && checkCast.outValue().getLocalInfo() == checkCast.object().getLocalInfo()) {
+ checkCast.outValue().replaceUsers(checkCast.object());
+ checkCast.object().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+ }
+ }
+ // Remove unused invoke results.
+ if (current.isInvoke() && !current.outValue().isUsed()) {
+ current.setOutValue(null);
+ }
}
DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code);
if (deadInstructionResult.isNotDead()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index f7a59dd..191d6af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -68,6 +68,7 @@
import com.android.tools.r8.utils.collections.LongLivedProgramMethodMultisetBuilder;
import com.android.tools.r8.utils.collections.ProgramMethodMultiset;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -722,7 +723,7 @@
int argumentRegisters;
DexType returnType;
Value returnValue;
- int returnValueUsersLeft;
+ int returnValueUniqueUsersLeft;
int pendingNewInstanceIndex = -1;
OutlineSpotter(ProgramMethod method, BasicBlock block) {
@@ -816,18 +817,16 @@
// Check if the current instruction can be included in the outline.
private boolean canIncludeInstruction(Instruction instruction) {
// Find the users of the active out-value (potential return value).
- int returnValueUsersLeftIfIncluded = returnValueUsersLeft;
- if (returnValue != null) {
- for (Value value : instruction.inValues()) {
- if (value.getAliasedValue() == returnValue) {
- returnValueUsersLeftIfIncluded--;
- }
- }
+ int returnValueUniqueUsersLeftIfIncluded = returnValueUniqueUsersLeft;
+ if (returnValue != null
+ && Iterables.any(
+ instruction.inValues(), value -> value.getAliasedValue() == returnValue)) {
+ returnValueUniqueUsersLeftIfIncluded--;
}
// If this instruction has an out-value, but the previous one is still active end the
// outline.
- if (instruction.outValue() != null && returnValueUsersLeftIfIncluded > 0) {
+ if (instruction.outValue() != null && returnValueUniqueUsersLeftIfIncluded > 0) {
return false;
}
@@ -1061,9 +1060,9 @@
}
private void updateReturnValueState(Value newReturnValue, DexType newReturnType) {
- returnValueUsersLeft = newReturnValue.numberOfAllUsers();
+ returnValueUniqueUsersLeft = newReturnValue.numberOfAllUsers();
// If the return value is not used don't track it.
- if (returnValueUsersLeft == 0) {
+ if (returnValueUniqueUsersLeft == 0) {
returnValue = null;
returnType = appView.dexItemFactory().voidType;
} else {
@@ -1073,9 +1072,9 @@
}
private void adjustReturnValueUsersLeft(int change) {
- returnValueUsersLeft += change;
- assert returnValueUsersLeft >= 0;
- if (returnValueUsersLeft == 0) {
+ returnValueUniqueUsersLeft += change;
+ assert returnValueUniqueUsersLeft >= 0;
+ if (returnValueUniqueUsersLeft == 0) {
returnValue = null;
returnType = appView.dexItemFactory().voidType;
}
@@ -1127,7 +1126,7 @@
argumentRegisters = 0;
returnType = appView.dexItemFactory().voidType;
returnValue = null;
- returnValueUsersLeft = 0;
+ returnValueUniqueUsersLeft = 0;
pendingNewInstanceIndex = -1;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 07ce458..eb6014b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -20,6 +20,7 @@
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
import static com.android.tools.r8.ir.code.Opcodes.RETURN;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.AppView;
@@ -34,7 +35,6 @@
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.DirectMappedDexApplication;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
@@ -79,6 +79,8 @@
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
+import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.IllegalInvokeWithImpreciseParameterTypeReason;
import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingContentsForEnumValuesArrayReason;
@@ -92,11 +94,11 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableMap;
@@ -136,6 +138,10 @@
private final ProgramMethodSet methodsDependingOnLibraryModelisation =
ProgramMethodSet.createConcurrent();
+ // Map from checkNotNull() methods to the enums that use the given method.
+ private final ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods =
+ ProgramMethodMap.createConcurrent();
+
private final DexEncodedField ordinalField;
private EnumUnboxingRewriter enumUnboxerRewriter;
@@ -518,31 +524,28 @@
enumUnboxingCandidatesInfo.clear();
// Update keep info on any of the enum methods of the removed classes.
updateKeepInfo(enumsToUnbox);
- DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
- FieldAccessInfoCollectionModifier.builder();
EnumUnboxingUtilityClasses utilityClasses =
EnumUnboxingUtilityClasses.builder(appView)
- .synthesizeEnumUnboxingUtilityClasses(
- enumClassesToUnbox,
- enumDataMap,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder)
- .build();
- utilityClasses.forEach(
- utilityClass -> utilityClass.getDefinition().forEachProgramMethod(postBuilder::add));
+ .synthesizeEnumUnboxingUtilityClasses(enumClassesToUnbox, enumDataMap)
+ .build(converter, executorService);
- fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
EnumUnboxingTreeFixer.Result treeFixerResult =
- new EnumUnboxingTreeFixer(appView, enumDataMap, enumClassesToUnbox, utilityClasses)
+ new EnumUnboxingTreeFixer(
+ appView, checkNotNullMethods, enumDataMap, enumClassesToUnbox, utilityClasses)
.fixupTypeReferences(converter, executorService);
EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
enumUnboxerRewriter =
- new EnumUnboxingRewriter(appView, converter, enumUnboxingLens, enumDataMap, utilityClasses);
+ new EnumUnboxingRewriter(
+ appView,
+ treeFixerResult.getCheckNotNullToCheckNotZeroMapping(),
+ converter,
+ enumUnboxingLens,
+ enumDataMap,
+ utilityClasses);
appView.setUnboxedEnums(enumDataMap);
GraphLens previousLens = appView.graphLens();
- appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
+ appView.rewriteWithLens(enumUnboxingLens);
updateOptimizationInfos(executorService, feedback, treeFixerResult.getPrunedItems());
postBuilder.put(dependencies);
// Methods depending on library modelisation need to be reprocessed so they are peephole
@@ -789,11 +792,6 @@
return OptionalInt.empty();
}
- public Constraint constraintForEnumUnboxing(
- DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) {
- return useRegistry.computeConstraint(method.asProgramMethod(appView));
- }
-
public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) {
return;
@@ -1218,6 +1216,24 @@
return Reason.INVALID_INIT;
}
}
+
+ // Check if this is a checkNotNull() user. In this case, we can create a copy of the method
+ // that takes an int instead of java.lang.Object and call that method instead.
+ EnumUnboxerMethodClassification classification =
+ singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
+ if (classification.isCheckNotNullClassification()) {
+ CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+ classification.asCheckNotNullClassification();
+ if (checkNotNullClassification.isUseEligibleForUnboxing(
+ invoke.asInvokeStatic(), enumValue)) {
+ checkNotNullMethods
+ .computeIfAbsent(
+ singleTarget.asProgramMethod(), ignoreKey(Sets::newConcurrentHashSet))
+ .add(enumClass);
+ return Reason.ELIGIBLE;
+ }
+ }
+
// Check that the enum-value only flows into parameters whose type exactly matches the
// enum's type.
for (int i = 0; i < singleTarget.getParameters().size(); i++) {
@@ -1473,13 +1489,6 @@
return Sets.newIdentityHashSet();
}
- public void synthesizeUtilityMethods(IRConverter converter, ExecutorService executorService)
- throws ExecutionException {
- if (enumUnboxerRewriter != null) {
- enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(converter, executorService);
- }
- }
-
public void unsetRewriter() {
enumUnboxerRewriter = null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 58cb128..a5978fa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -10,12 +10,16 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.NestedGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeMap;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
import com.google.common.collect.ImmutableMap;
import java.util.IdentityHashMap;
@@ -29,10 +33,10 @@
EnumUnboxingLens(
AppView<?> appView,
BidirectionalOneToOneMap<DexField, DexField> fieldMap,
- BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap,
+ BidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> methodMap,
Map<DexType, DexType> typeMap,
Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod) {
- super(appView, fieldMap, methodMap, typeMap);
+ super(appView, fieldMap, methodMap::getRepresentativeValue, typeMap, methodMap);
this.prototypeChangesPerMethod = prototypeChangesPerMethod;
}
@@ -67,8 +71,8 @@
private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
private final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
new BidirectionalOneToOneHashMap<>();
- private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
- new BidirectionalOneToOneHashMap<>();
+ private final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod>
+ newMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
new IdentityHashMap<>();
@@ -135,6 +139,19 @@
.withExtraUnusedNullParameters(numberOfExtraNullParameters));
}
+ void recordCheckNotZeroMethod(
+ ProgramMethod checkNotNullMethod, ProgramMethod checkNotZeroMethod) {
+ DexMethod originalCheckNotNullMethodSignature =
+ newMethodSignatures.getKeyOrDefault(
+ checkNotNullMethod.getReference(), checkNotNullMethod.getReference());
+ newMethodSignatures.put(
+ originalCheckNotNullMethodSignature, checkNotNullMethod.getReference());
+ newMethodSignatures.put(
+ originalCheckNotNullMethodSignature, checkNotZeroMethod.getReference());
+ newMethodSignatures.setRepresentative(
+ originalCheckNotNullMethodSignature, checkNotNullMethod.getReference());
+ }
+
public EnumUnboxingLens build(AppView<?> appView) {
assert !typeMap.isEmpty();
return new EnumUnboxingLens(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index fe3049f..287a4d6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -4,27 +4,17 @@
package com.android.tools.r8.ir.optimize.enums;
-import com.android.tools.r8.cf.CfVersion;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMember;
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.GenericSignature.MethodTypeSignature;
-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.analysis.type.ArrayTypeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.ArrayAccess;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
@@ -44,32 +34,23 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
-import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
+import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
public class EnumUnboxingRewriter {
- public static final String ENUM_UNBOXING_UTILITY_METHOD_PREFIX = "$enumboxing$";
- private static final CfVersion REQUIRED_CLASS_FILE_VERSION = CfVersion.V1_8;
-
private final AppView<AppInfoWithLiveness> appView;
+ private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
private final IRConverter converter;
private final DexItemFactory factory;
private final InternalOptions options;
@@ -77,56 +58,21 @@
private final EnumUnboxingLens enumUnboxingLens;
private final EnumUnboxingUtilityClasses utilityClasses;
- private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
-
- private final DexMethod ordinalUtilityMethod;
- private final DexMethod equalsUtilityMethod;
- private final DexMethod compareToUtilityMethod;
- private final DexMethod zeroCheckMethod;
- private final DexMethod zeroCheckMessageMethod;
-
EnumUnboxingRewriter(
AppView<AppInfoWithLiveness> appView,
+ Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
IRConverter converter,
EnumUnboxingLens enumUnboxingLens,
EnumDataMap unboxedEnumsInstanceFieldData,
EnumUnboxingUtilityClasses utilityClasses) {
this.appView = appView;
+ this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
this.converter = converter;
this.factory = appView.dexItemFactory();
this.options = appView.options();
this.enumUnboxingLens = enumUnboxingLens;
this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
this.utilityClasses = utilityClasses;
-
- // Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
- DexType sharedEnumUnboxingUtilityType = utilityClasses.getSharedUtilityClass().getType();
- this.ordinalUtilityMethod =
- factory.createMethod(
- sharedEnumUnboxingUtilityType,
- factory.createProto(factory.intType, factory.intType),
- ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
- this.equalsUtilityMethod =
- factory.createMethod(
- sharedEnumUnboxingUtilityType,
- factory.createProto(factory.booleanType, factory.intType, factory.intType),
- ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
- this.compareToUtilityMethod =
- factory.createMethod(
- sharedEnumUnboxingUtilityType,
- factory.createProto(factory.intType, factory.intType, factory.intType),
- ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
- // Custom methods for Object#getClass without outValue and Objects.requireNonNull.
- this.zeroCheckMethod =
- factory.createMethod(
- sharedEnumUnboxingUtilityType,
- factory.createProto(factory.voidType, factory.intType),
- ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
- this.zeroCheckMessageMethod =
- factory.createMethod(
- sharedEnumUnboxingUtilityType,
- factory.createProto(factory.voidType, factory.intType, factory.stringType),
- ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
}
private LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
@@ -171,69 +117,67 @@
// - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
// also in the unboxed enum class.
if (instruction.isInvokeMethodWithReceiver()) {
- InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
- DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
- DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+ InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+ DexType enumType = getEnumTypeOrNull(invoke.getReceiver(), convertedEnums);
+ DexMethod invokedMethod = invoke.getInvokedMethod();
if (enumType != null) {
if (invokedMethod == factory.enumMembers.ordinalMethod
|| invokedMethod.match(factory.enumMembers.hashCode)) {
replaceEnumInvoke(
- iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
+ iterator, invoke, getSharedUtilityClass().ensureOrdinalMethod(appView));
continue;
} else if (invokedMethod.match(factory.enumMembers.equals)) {
replaceEnumInvoke(
- iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
+ iterator, invoke, getSharedUtilityClass().ensureEqualsMethod(appView));
continue;
} else if (invokedMethod == factory.enumMembers.compareTo
|| invokedMethod == factory.enumMembers.compareToWithObject) {
replaceEnumInvoke(
- iterator,
- invokeMethod,
- getSharedUtilityClass()
- .ensureCompareToMethod(appView, converter, methodProcessor));
+ iterator, invoke, getSharedUtilityClass().ensureCompareToMethod(appView));
continue;
} else if (invokedMethod == factory.enumMembers.nameMethod) {
- rewriteNameMethod(iterator, invokeMethod, enumType);
+ rewriteNameMethod(iterator, invoke, enumType, methodProcessor);
continue;
} else if (invokedMethod.match(factory.enumMembers.toString)) {
DexMethod lookupMethod = enumUnboxingLens.lookupMethod(invokedMethod);
// If the lookupMethod is different, then a toString method was on the enumType
// class, which was moved, and the lens code rewriter will rewrite the invoke to
// that method.
- if (invokeMethod.isInvokeSuper() || lookupMethod == invokedMethod) {
- rewriteNameMethod(iterator, invokeMethod, enumType);
+ if (invoke.isInvokeSuper() || lookupMethod == invokedMethod) {
+ rewriteNameMethod(iterator, invoke, enumType, methodProcessor);
continue;
}
} else if (invokedMethod == factory.objectMembers.getClass) {
- assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
+ assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
replaceEnumInvoke(
- iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+ iterator, invoke, getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
continue;
}
} else if (invokedMethod == factory.stringBuilderMethods.appendObject
|| invokedMethod == factory.stringBufferMethods.appendObject) {
// Rewrites stringBuilder.append(enumInstance) as if it was
// stringBuilder.append(String.valueOf(unboxedEnumInstance));
- Value enumArg = invokeMethod.getArgument(1);
+ Value enumArg = invoke.getArgument(1);
DexType enumArgType = getEnumTypeOrNull(enumArg, convertedEnums);
if (enumArgType != null) {
- DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumArgType);
+ ProgramMethod stringValueOfMethod =
+ getLocalUtilityClass(enumArgType).ensureStringValueOfMethod(appView);
InvokeStatic toStringInvoke =
InvokeStatic.builder()
.setMethod(stringValueOfMethod)
.setSingleArgument(enumArg)
.setFreshOutValue(appView, code)
- .setPosition(invokeMethod)
+ .setPosition(invoke)
.build();
DexMethod newAppendMethod =
invokedMethod == factory.stringBuilderMethods.appendObject
? factory.stringBuilderMethods.appendString
: factory.stringBufferMethods.appendString;
List<Value> arguments =
- ImmutableList.of(invokeMethod.getReceiver(), toStringInvoke.outValue());
+ ImmutableList.of(invoke.getReceiver(), toStringInvoke.outValue());
InvokeVirtual invokeAppendString =
- new InvokeVirtual(newAppendMethod, invokeMethod.clearOutValue(), arguments);
- invokeAppendString.setPosition(invokeMethod.getPosition());
+ new InvokeVirtual(newAppendMethod, invoke.clearOutValue(), arguments);
+ invokeAppendString.setPosition(invoke.getPosition());
iterator.replaceCurrentInstruction(toStringInvoke);
if (block.hasCatchHandlers()) {
iterator
@@ -248,7 +192,13 @@
}
} else if (instruction.isInvokeStatic()) {
rewriteInvokeStatic(
- instruction.asInvokeStatic(), code, context, convertedEnums, iterator, affectedPhis);
+ instruction.asInvokeStatic(),
+ code,
+ context,
+ convertedEnums,
+ iterator,
+ affectedPhis,
+ methodProcessor);
}
if (instruction.isStaticGet()) {
StaticGet staticGet = instruction.asStaticGet();
@@ -301,14 +251,16 @@
InstanceGet instanceGet = instruction.asInstanceGet();
DexType holder = instanceGet.getField().holder;
if (unboxedEnumsData.isUnboxedEnum(holder)) {
- DexMethod fieldMethod = computeInstanceFieldMethod(instanceGet.getField());
+ ProgramMethod fieldMethod =
+ ensureInstanceFieldMethod(instanceGet.getField(), methodProcessor);
Value rewrittenOutValue =
code.createValue(
- TypeElement.fromDexType(
- fieldMethod.proto.returnType, Nullability.maybeNull(), appView));
+ TypeElement.fromDexType(fieldMethod.getReturnType(), maybeNull(), appView));
InvokeStatic invoke =
new InvokeStatic(
- fieldMethod, rewrittenOutValue, ImmutableList.of(instanceGet.object()));
+ fieldMethod.getReference(),
+ rewrittenOutValue,
+ ImmutableList.of(instanceGet.object()));
iterator.replaceCurrentInstruction(invoke);
if (unboxedEnumsData.isUnboxedEnum(instanceGet.getField().type)) {
convertedEnums.put(invoke, instanceGet.getField().type);
@@ -350,7 +302,8 @@
ProgramMethod context,
Map<Instruction, DexType> convertedEnums,
InstructionListIterator instructionIterator,
- Set<Phi> affectedPhis) {
+ Set<Phi> affectedPhis,
+ MethodProcessor methodProcessor) {
DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
if (singleTarget == null) {
return;
@@ -368,7 +321,7 @@
if (!unboxedEnumsData.isUnboxedEnum(enumType)) {
return;
}
- DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
+ ProgramMethod valueOfMethod = getLocalUtilityClass(enumType).ensureValueOfMethod(appView);
Value outValue = invoke.outValue();
Value rewrittenOutValue = null;
if (outValue != null) {
@@ -377,7 +330,7 @@
}
InvokeStatic replacement =
new InvokeStatic(
- valueOfMethod,
+ valueOfMethod.getReference(),
rewrittenOutValue,
Collections.singletonList(invoke.inValues().get(1)));
instructionIterator.replaceCurrentInstruction(replacement);
@@ -394,7 +347,9 @@
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
replaceEnumInvoke(
- instructionIterator, invoke, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+ instructionIterator,
+ invoke,
+ getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
}
} else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
assert invoke.arguments().size() == 2;
@@ -404,8 +359,7 @@
replaceEnumInvoke(
instructionIterator,
invoke,
- zeroCheckMessageMethod,
- m -> synthesizeZeroCheckMessageMethod());
+ getSharedUtilityClass().ensureCheckNotZeroWithMessageMethod(appView));
}
}
return;
@@ -418,9 +372,11 @@
Value argument = invoke.getFirstArgument();
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
- DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumType);
+ ProgramMethod stringValueOfMethod =
+ getLocalUtilityClass(enumType).ensureStringValueOfMethod(appView);
instructionIterator.replaceCurrentInstruction(
- new InvokeStatic(stringValueOfMethod, invoke.outValue(), invoke.arguments()));
+ new InvokeStatic(
+ stringValueOfMethod.getReference(), invoke.outValue(), invoke.arguments()));
}
}
return;
@@ -441,6 +397,29 @@
}
return;
}
+
+ if (singleTarget.isProgramMethod()) {
+ EnumUnboxerMethodClassification classification =
+ singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
+ if (classification.isCheckNotNullClassification()) {
+ CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+ classification.asCheckNotNullClassification();
+ Value argument = invoke.getArgument(checkNotNullClassification.getArgumentIndex());
+ DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+ if (enumType != null) {
+ InvokeStatic replacement =
+ InvokeStatic.builder()
+ .setMethod(checkNotNullToCheckNotZeroMapping.get(singleTarget.getReference()))
+ .setArguments(invoke.arguments())
+ .setPosition(invoke.getPosition())
+ .build();
+ instructionIterator.replaceCurrentInstruction(replacement);
+ convertedEnums.put(replacement, enumType);
+ }
+ } else {
+ assert !checkNotNullToCheckNotZeroMapping.containsKey(singleTarget.getReference());
+ }
+ }
}
private void removeRedundantValuesArrayCloning(
@@ -464,11 +443,15 @@
}
private void rewriteNameMethod(
- InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
- DexMethod toStringMethod =
- computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
+ InstructionListIterator iterator,
+ InvokeMethodWithReceiver invoke,
+ DexType enumType,
+ MethodProcessor methodProcessor) {
+ ProgramMethod toStringMethod =
+ getLocalUtilityClass(enumType)
+ .ensureGetInstanceFieldMethod(appView, factory.enumMembers.nameField);
iterator.replaceCurrentInstruction(
- new InvokeStatic(toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
+ new InvokeStatic(toStringMethod.getReference(), invoke.outValue(), invoke.arguments()));
}
private Value fixNullsInBlockPhis(IRCode code, BasicBlock block, Value zeroConstValue) {
@@ -496,32 +479,22 @@
return iterator.insertConstIntInstruction(code, options, 0);
}
- private DexMethod computeInstanceFieldMethod(DexField field) {
+ private ProgramMethod ensureInstanceFieldMethod(DexField field, MethodProcessor methodProcessor) {
EnumInstanceFieldKnownData enumFieldKnownData =
unboxedEnumsData.getInstanceFieldData(field.holder, field);
if (enumFieldKnownData.isOrdinal()) {
- utilityMethods.computeIfAbsent(ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
- return ordinalUtilityMethod;
+ return getSharedUtilityClass().ensureOrdinalMethod(appView);
}
- return computeInstanceFieldUtilityMethod(field.holder, field);
+ return getLocalUtilityClass(field.getHolderType()).ensureGetInstanceFieldMethod(appView, field);
}
private void replaceEnumInvoke(
InstructionListIterator iterator, InvokeMethod invoke, ProgramMethod method) {
- replaceEnumInvoke(iterator, invoke, method.getReference(), null);
- }
-
- private void replaceEnumInvoke(
- InstructionListIterator iterator,
- InvokeMethod invoke,
- DexMethod method,
- Function<DexMethod, DexEncodedMethod> synthesizor) {
- if (synthesizor != null) {
- utilityMethods.computeIfAbsent(method, synthesizor);
- }
InvokeStatic replacement =
new InvokeStatic(
- method, invoke.hasUnusedOutValue() ? null : invoke.outValue(), invoke.arguments());
+ method.getReference(),
+ invoke.hasUnusedOutValue() ? null : invoke.outValue(),
+ invoke.arguments());
assert !replacement.hasOutValue()
|| !replacement.getInvokedMethod().getReturnType().isVoidType();
iterator.replaceCurrentInstruction(replacement);
@@ -555,57 +528,6 @@
return unboxedEnumsData.isUnboxedEnum(enumType) ? enumType : null;
}
- public static String compatibleName(DexType type) {
- return type.toSourceString().replace('.', '$');
- }
-
- private DexMethod computeInstanceFieldUtilityMethod(DexType enumType, DexField field) {
- assert unboxedEnumsData.isUnboxedEnum(enumType);
- assert field.holder == enumType || field.holder == factory.enumType;
- String methodName =
- "get"
- + (enumType == field.holder ? "" : "Enum$")
- + field.name
- + "$$"
- + compatibleName(enumType);
- DexMethod fieldMethod =
- factory.createMethod(
- utilityClasses.getLocalUtilityClass(enumType).getType(),
- factory.createProto(field.type, factory.intType),
- methodName);
- utilityMethods.computeIfAbsent(
- fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field, null));
- return fieldMethod;
- }
-
- private DexMethod computeStringValueOfUtilityMethod(DexType enumType) {
- // TODO(b/167994636): remove duplication between instance field name read and this method.
- assert unboxedEnumsData.isUnboxedEnum(enumType);
- String methodName = "string$valueOf$" + compatibleName(enumType);
- DexMethod fieldMethod =
- factory.createMethod(
- utilityClasses.getLocalUtilityClass(enumType).getType(),
- factory.createProto(factory.stringType, factory.intType),
- methodName);
- AbstractValue nullString =
- appView.abstractValueFactory().createSingleStringValue(factory.createString("null"));
- utilityMethods.computeIfAbsent(
- fieldMethod,
- m -> synthesizeInstanceFieldMethod(m, enumType, factory.enumMembers.nameField, nullString));
- return fieldMethod;
- }
-
- private DexMethod computeValueOfUtilityMethod(DexType enumType) {
- assert unboxedEnumsData.isUnboxedEnum(enumType);
- DexMethod valueOf =
- factory.createMethod(
- utilityClasses.getLocalUtilityClass(enumType).getType(),
- factory.createProto(factory.intType, factory.stringType),
- "valueOf" + compatibleName(enumType));
- utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
- return valueOf;
- }
-
private DexType getEnumTypeOrNull(ArrayAccess arrayAccess) {
ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
if (arrayType == null) {
@@ -622,121 +544,4 @@
DexType classType = baseType.asClassType().getClassType();
return unboxedEnumsData.isUnboxedEnum(classType) ? classType : null;
}
-
- void synthesizeEnumUnboxingUtilityMethods(IRConverter converter, ExecutorService executorService)
- throws ExecutionException {
- // Append to the various utility classes, in deterministic order, the utility methods and
- // fields required.
- Map<DexType, List<DexEncodedMethod>> methodMap = triageEncodedMembers(utilityMethods.values());
- if (methodMap.isEmpty()) {
- return;
- }
- SortedProgramMethodSet wave = SortedProgramMethodSet.create();
- methodMap.forEach(
- (type, methodsSorted) -> {
- DexProgramClass utilityClass = appView.definitionFor(type).asProgramClass();
- assert utilityClass != null;
- utilityClass.addDirectMethods(methodsSorted);
- for (DexEncodedMethod dexEncodedMethod : methodsSorted) {
- wave.add(new ProgramMethod(utilityClass, dexEncodedMethod));
- }
- });
- converter.processMethodsConcurrently(wave, executorService);
- }
-
- <R extends DexMember<T, R>, T extends DexEncodedMember<T, R>>
- Map<DexType, List<T>> triageEncodedMembers(Collection<T> encodedMembers) {
- if (encodedMembers.isEmpty()) {
- return Collections.emptyMap();
- }
- Map<DexType, List<T>> encodedMembersMap = new IdentityHashMap<>();
- // We compute encodedMembers by types.
- for (T encodedMember : encodedMembers) {
- List<T> members =
- encodedMembersMap.computeIfAbsent(
- encodedMember.getHolderType(), ignored -> new ArrayList<>());
- members.add(encodedMember);
- }
- // We make the order deterministic.
- for (List<T> value : encodedMembersMap.values()) {
- value.sort(Comparator.comparing(DexEncodedMember::getReference));
- }
- return encodedMembersMap;
- }
-
- private DexEncodedMethod synthesizeInstanceFieldMethod(
- DexMethod method, DexType enumType, DexField field, AbstractValue nullValue) {
- assert method.proto.returnType == field.type;
- assert unboxedEnumsData.getInstanceFieldData(enumType, field).isMapping();
- CfCode cfCode =
- new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
- appView,
- method.holder,
- field.type,
- unboxedEnumsData.getInstanceFieldData(enumType, field).asEnumFieldMappingData(),
- nullValue)
- .generateCfCode();
- return synthesizeUtilityMethod(cfCode, method);
- }
-
- private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
- assert method.proto.returnType == factory.intType;
- assert unboxedEnumsData
- .getInstanceFieldData(enumType, factory.enumMembers.nameField)
- .isMapping();
- CfCode cfCode =
- new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
- appView,
- method.holder,
- enumType,
- unboxedEnumsData
- .getInstanceFieldData(enumType, factory.enumMembers.nameField)
- .asEnumFieldMappingData())
- .generateCfCode();
- return synthesizeUtilityMethod(cfCode, method);
- }
-
- private DexEncodedMethod synthesizeZeroCheckMethod() {
- CfCode cfCode =
- EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
- return synthesizeUtilityMethod(cfCode, zeroCheckMethod);
- }
-
- private DexEncodedMethod synthesizeZeroCheckMessageMethod() {
- CfCode cfCode =
- EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(
- appView.options(), zeroCheckMessageMethod);
- return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod);
- }
-
- private DexEncodedMethod synthesizeOrdinalMethod() {
- CfCode cfCode =
- EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
- return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod);
- }
-
- private DexEncodedMethod synthesizeEqualsMethod() {
- CfCode cfCode =
- EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
- return synthesizeUtilityMethod(cfCode, equalsUtilityMethod);
- }
-
- private DexEncodedMethod synthesizeCompareToMethod() {
- CfCode cfCode =
- EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
- appView.options(), compareToUtilityMethod);
- return synthesizeUtilityMethod(cfCode, compareToUtilityMethod);
- }
-
- private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method) {
- return new DexEncodedMethod(
- method,
- MethodAccessFlags.createPublicStaticSynthetic(),
- MethodTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- cfCode,
- true,
- REQUIRED_CLASS_FILE_VERSION);
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 243e721..9331f51 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -18,6 +19,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
+import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
@@ -36,19 +38,28 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.code.CheckNotZeroCode;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ImmutableArrayUtils;
import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
@@ -62,6 +73,7 @@
private final EnumUnboxingLens.Builder lensBuilder;
private final AppView<AppInfoWithLiveness> appView;
+ private final ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods;
private final DexItemFactory factory;
private final EnumDataMap enumDataMap;
private final Set<DexProgramClass> unboxedEnums;
@@ -69,10 +81,12 @@
EnumUnboxingTreeFixer(
AppView<AppInfoWithLiveness> appView,
+ ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods,
EnumDataMap enumDataMap,
Set<DexProgramClass> unboxedEnums,
EnumUnboxingUtilityClasses utilityClasses) {
this.appView = appView;
+ this.checkNotNullMethods = checkNotNullMethods;
this.enumDataMap = enumDataMap;
this.factory = appView.dexItemFactory();
this.lensBuilder =
@@ -117,9 +131,82 @@
}
}
- return new Result(lensBuilder.build(appView), prunedItemsBuilder.build());
+ // Create mapping from checkNotNull() to checkNotZero() methods.
+ Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping =
+ duplicateCheckNotNullMethods(converter, executorService);
+
+ return new Result(
+ checkNotNullToCheckNotZeroMapping, lensBuilder.build(appView), prunedItemsBuilder.build());
}
+ private Map<DexMethod, DexMethod> duplicateCheckNotNullMethods(
+ IRConverter converter, ExecutorService executorService) throws ExecutionException {
+ Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = new IdentityHashMap<>();
+ ProcessorContext processorContext = appView.createProcessorContext();
+ OneTimeMethodProcessor.Builder methodProcessorBuilder =
+ OneTimeMethodProcessor.builder(processorContext);
+
+ // Only duplicate checkNotNull() methods that are required for enum unboxing.
+ checkNotNullMethods.removeIf(
+ (checkNotNullMethod, dependentEnums) ->
+ !SetUtils.containsAnyOf(unboxedEnums, dependentEnums));
+
+ // For each checkNotNull() method, synthesize a free flowing static checkNotZero() method that
+ // takes an int instead of an Object with the same implementation.
+ checkNotNullMethods.forEach(
+ (checkNotNullMethod, dependentEnums) -> {
+ CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+ checkNotNullMethod
+ .getOptimizationInfo()
+ .getEnumUnboxerMethodClassification()
+ .asCheckNotNullClassification();
+ DexProto newProto =
+ factory.createProto(
+ factory.voidType,
+ ImmutableArrayUtils.set(
+ checkNotNullMethod.getParameters().getBacking(),
+ checkNotNullClassification.getArgumentIndex(),
+ factory.intType));
+ ProgramMethod checkNotZeroMethod =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ SyntheticKind.ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD,
+ // Use the context of the checkNotNull() method to ensure the method is placed
+ // in the same feature split.
+ processorContext
+ .createMethodProcessingContext(checkNotNullMethod)
+ .createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setClassFileVersion(
+ checkNotNullMethod
+ .getDefinition()
+ .getClassFileVersionOrElse(null))
+ .setCode(method -> new CheckNotZeroCode(checkNotNullMethod))
+ .setProto(newProto));
+ checkNotNullToCheckNotZeroMapping.put(
+ checkNotNullMethod.getReference(), checkNotZeroMethod.getReference());
+ lensBuilder.recordCheckNotZeroMethod(checkNotNullMethod, checkNotZeroMethod);
+ methodProcessorBuilder.add(checkNotZeroMethod);
+ });
+
+ // Convert each of the synthesized methods. These methods are converted eagerly, since their
+ // code objects are of type 'CheckNotZeroCode', which implements most methods using throw new
+ // Unreachable().
+ OneTimeMethodProcessor methodProcessor = methodProcessorBuilder.build();
+ methodProcessor.forEachWaveWithExtension(
+ (method, methodProcessingContext) ->
+ converter.processDesugaredMethod(
+ method, OptimizationFeedback.getSimple(), methodProcessor, methodProcessingContext),
+ executorService);
+
+ return checkNotNullToCheckNotZeroMapping;
+ }
+
+
private void fixupEnumClassInitializers(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
DexEncodedField ordinalField =
@@ -367,12 +454,13 @@
Predicate<DexMethod> availableMethodSignatures) {
DexMethod methodReference = method.getReference();
- // Create a new, fresh method signature on the local utility class.
+ // Create a new, fresh method signature on the local utility class. We prefix the method by "_"
+ // such that this does not collide with the utility methods we synthesize for unboxing.
DexMethod newMethod =
method.getDefinition().isClassInitializer()
? factory.createClassInitializer(localUtilityClass.getType())
: factory.createFreshMethodNameWithoutHolder(
- method.getName().toString(),
+ "_" + method.getName().toString(),
fixupProto(
method.getAccessFlags().isStatic()
? method.getProto()
@@ -531,14 +619,23 @@
public static class Result {
+ private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
private final EnumUnboxingLens lens;
private final PrunedItems prunedItems;
- Result(EnumUnboxingLens lens, PrunedItems prunedItems) {
+ Result(
+ Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
+ EnumUnboxingLens lens,
+ PrunedItems prunedItems) {
+ this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
this.lens = lens;
this.prunedItems = prunedItems;
}
+ Map<DexMethod, DexMethod> getCheckNotNullToCheckNotZeroMapping() {
+ return checkNotNullToCheckNotZeroMapping;
+ }
+
EnumUnboxingLens getLens() {
return lens;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
index 6026c71..34dd886 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
@@ -4,9 +4,23 @@
package com.android.tools.r8.ir.optimize.enums;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public abstract class EnumUnboxingUtilityClass {
+ private final DexProgramClass synthesizingContext;
+
+ EnumUnboxingUtilityClass(DexProgramClass synthesizingContext) {
+ this.synthesizingContext = synthesizingContext;
+ }
+
+ public abstract void ensureMethods(AppView<AppInfoWithLiveness> appView);
+
public abstract DexProgramClass getDefinition();
+
+ public final DexProgramClass getSynthesizingContext() {
+ return synthesizingContext;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
index 05d4955..8dd1398 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -8,12 +8,17 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableMap;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
public class EnumUnboxingUtilityClasses {
@@ -61,38 +66,63 @@
private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
private SharedEnumUnboxingUtilityClass sharedUtilityClass;
+ private final FieldAccessInfoCollectionModifier.Builder
+ fieldAccessInfoCollectionModifierBuilder = FieldAccessInfoCollectionModifier.builder();
+
public Builder(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
}
public Builder synthesizeEnumUnboxingUtilityClasses(
- Set<DexProgramClass> enumsToUnbox,
- EnumDataMap enumDataMap,
- DirectMappedDexApplication.Builder appBuilder,
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ Set<DexProgramClass> enumsToUnbox, EnumDataMap enumDataMap) {
SharedEnumUnboxingUtilityClass sharedUtilityClass =
SharedEnumUnboxingUtilityClass.builder(
appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder)
.build();
ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
- createLocalUtilityClasses(enumsToUnbox, appBuilder);
+ createLocalUtilityClasses(enumsToUnbox, enumDataMap);
this.localUtilityClasses = localUtilityClasses;
this.sharedUtilityClass = sharedUtilityClass;
return this;
}
- public EnumUnboxingUtilityClasses build() {
- return new EnumUnboxingUtilityClasses(sharedUtilityClass, localUtilityClasses);
+ public EnumUnboxingUtilityClasses build(IRConverter converter, ExecutorService executorService)
+ throws ExecutionException {
+ EnumUnboxingUtilityClasses utilityClasses =
+ new EnumUnboxingUtilityClasses(sharedUtilityClass, localUtilityClasses);
+
+ // Extend the field access info collection with information about synthesized fields.
+ fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
+
+ // Create and process the utility methods.
+ OneTimeMethodProcessor.Builder methodProcessorBuilder =
+ OneTimeMethodProcessor.builder(appView.createProcessorContext());
+ utilityClasses.forEach(
+ utilityClass -> {
+ utilityClass.ensureMethods(appView);
+ utilityClass.getDefinition().forEachProgramMethod(methodProcessorBuilder::add);
+ });
+ OneTimeMethodProcessor methodProcessor = methodProcessorBuilder.build();
+ methodProcessor.forEachWaveWithExtension(
+ (method, methodProcessingContext) ->
+ converter.processDesugaredMethod(
+ method,
+ OptimizationFeedbackSimple.getInstance(),
+ methodProcessor,
+ methodProcessingContext),
+ executorService);
+ return utilityClasses;
}
private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> createLocalUtilityClasses(
- Set<DexProgramClass> enumsToUnbox, DirectMappedDexApplication.Builder appBuilder) {
+ Set<DexProgramClass> enumsToUnbox, EnumDataMap dataMap) {
ImmutableMap.Builder<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
ImmutableMap.builder();
for (DexProgramClass enumToUnbox : enumsToUnbox) {
+ EnumData data = dataMap.get(enumToUnbox);
localUtilityClasses.put(
enumToUnbox.getType(),
- LocalEnumUnboxingUtilityClass.builder(appView, enumToUnbox).build(appBuilder));
+ LocalEnumUnboxingUtilityClass.builder(appView, enumToUnbox, data).build());
}
return localUtilityClasses.build();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
index f4d4636..8dbd8a4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -4,20 +4,25 @@
package com.android.tools.r8.ir.optimize.enums;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collections;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
public class LocalEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
@@ -25,13 +30,108 @@
"$r8$EnumUnboxingLocalUtility";
private final DexProgramClass localUtilityClass;
+ private final EnumData data;
- public LocalEnumUnboxingUtilityClass(DexProgramClass localUtilityClass) {
+ public LocalEnumUnboxingUtilityClass(
+ DexProgramClass localUtilityClass, EnumData data, DexProgramClass synthesizingContext) {
+ super(synthesizingContext);
this.localUtilityClass = localUtilityClass;
+ this.data = data;
}
- public static Builder builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
- return new Builder(appView, enumToUnbox);
+ public static Builder builder(
+ AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox, EnumData data) {
+ return new Builder(appView, enumToUnbox, data);
+ }
+
+ @Override
+ public void ensureMethods(AppView<AppInfoWithLiveness> appView) {
+ data.instanceFieldMap.forEach(
+ (field, fieldData) -> {
+ if (fieldData.isMapping()) {
+ ensureGetInstanceFieldMethod(appView, field);
+ }
+ });
+ if (data.instanceFieldMap.containsKey(appView.dexItemFactory().enumMembers.nameField)) {
+ ensureStringValueOfMethod(appView);
+ ensureValueOfMethod(appView);
+ }
+ }
+
+ public ProgramMethod ensureGetInstanceFieldMethod(
+ AppView<AppInfoWithLiveness> appView,
+ DexField field) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ String fieldName = field.getName().toString();
+ DexString methodName;
+ if (field.getHolderType() == getSynthesizingContext().getType()) {
+ methodName =
+ dexItemFactory.createString(
+ "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
+ } else {
+ assert field == appView.dexItemFactory().enumMembers.nameField
+ || field == appView.dexItemFactory().enumMembers.ordinalField;
+ methodName = field.getName();
+ }
+ return internalEnsureMethod(
+ appView,
+ methodName,
+ dexItemFactory.createProto(field.getType(), dexItemFactory.intType),
+ method ->
+ new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
+ appView, getType(), data, field)
+ .generateCfCode());
+ }
+
+ public ProgramMethod ensureStringValueOfMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ AbstractValue defaultValue =
+ appView.abstractValueFactory().createSingleStringValue(dexItemFactory.createString("null"));
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.createString("stringValueOf"),
+ dexItemFactory.createProto(dexItemFactory.stringType, dexItemFactory.intType),
+ method ->
+ new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
+ appView, getType(), data, dexItemFactory.enumMembers.nameField, defaultValue)
+ .generateCfCode());
+ }
+
+ public ProgramMethod ensureValueOfMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.createString("valueOf"),
+ dexItemFactory.createProto(dexItemFactory.intType, dexItemFactory.stringType),
+ method ->
+ new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
+ appView,
+ getType(),
+ getSynthesizingContext().getType(),
+ data.getInstanceFieldData(dexItemFactory.enumMembers.nameField)
+ .asEnumFieldMappingData())
+ .generateCfCode());
+ }
+
+ private ProgramMethod internalEnsureMethod(
+ AppView<AppInfoWithLiveness> appView,
+ DexString methodName,
+ DexProto methodProto,
+ SyntheticCodeGenerator codeGenerator) {
+ return appView
+ .getSyntheticItems()
+ .ensureFixedClassMethod(
+ methodName,
+ methodProto,
+ SyntheticKind.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
+ getSynthesizingContext(),
+ appView,
+ emptyConsumer(),
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(codeGenerator)
+ .setClassFileVersion(CfVersion.V1_6));
}
@Override
@@ -46,49 +146,38 @@
public static class Builder {
private final AppView<AppInfoWithLiveness> appView;
- private final DexItemFactory dexItemFactory;
+ private final EnumData data;
private final DexProgramClass enumToUnbox;
private final DexType localUtilityClassType;
- private Builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+ private Builder(
+ AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox, EnumData data) {
this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
+ this.data = data;
this.enumToUnbox = enumToUnbox;
this.localUtilityClassType =
EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
- enumToUnbox, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX, dexItemFactory);
+ enumToUnbox, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX, appView.dexItemFactory());
assert appView.appInfo().definitionForWithoutExistenceAssert(localUtilityClassType) == null;
}
- LocalEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+ LocalEnumUnboxingUtilityClass build() {
DexProgramClass clazz = createClass();
- appBuilder.addSynthesizedClass(clazz);
- appView.appInfo().addSynthesizedClass(clazz, enumToUnbox);
- return new LocalEnumUnboxingUtilityClass(clazz);
+ return new LocalEnumUnboxingUtilityClass(clazz, data, enumToUnbox);
}
private DexProgramClass createClass() {
- return new DexProgramClass(
- localUtilityClassType,
- null,
- new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
- ClassAccessFlags.createPublicFinalSynthetic(),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- null,
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
+ DexProgramClass clazz =
+ appView
+ .getSyntheticItems()
+ .createFixedClass(
+ SyntheticKind.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
+ enumToUnbox,
+ appView,
+ builder -> builder.setUseSortedMethodBacking(true));
+ assert clazz.getAccessFlags().equals(ClassAccessFlags.createPublicFinalSynthetic());
+ return clazz;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 3e556e6..5cbab1f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -25,6 +25,8 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
@@ -34,11 +36,9 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.ConsumerUtils;
import com.google.common.collect.ImmutableList;
@@ -51,15 +51,14 @@
public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
private final DexProgramClass sharedUtilityClass;
- private final DexProgramClass synthesizingContext;
private final ProgramMethod valuesMethod;
public SharedEnumUnboxingUtilityClass(
DexProgramClass sharedUtilityClass,
DexProgramClass synthesizingContext,
ProgramMethod valuesMethod) {
+ super(synthesizingContext);
this.sharedUtilityClass = sharedUtilityClass;
- this.synthesizingContext = synthesizingContext;
this.valuesMethod = valuesMethod;
}
@@ -72,11 +71,69 @@
appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
}
- public ProgramMethod ensureCompareToMethod(
- AppView<AppInfoWithLiveness> appView,
- IRConverter converter,
- MethodProcessor methodProcessor) {
+ @Override
+ public void ensureMethods(AppView<AppInfoWithLiveness> appView) {
+ ensureCheckNotZeroMethod(appView);
+ ensureCheckNotZeroWithMessageMethod(appView);
+ ensureCompareToMethod(appView);
+ ensureEqualsMethod(appView);
+ ensureOrdinalMethod(appView);
+ }
+
+ public ProgramMethod ensureCheckNotZeroMethod(AppView<AppInfoWithLiveness> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.createString("checkNotZero"),
+ dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.intType),
+ method -> EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), method));
+ }
+
+ public ProgramMethod ensureCheckNotZeroWithMessageMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.createString("checkNotZero"),
+ dexItemFactory.createProto(
+ dexItemFactory.voidType, dexItemFactory.intType, dexItemFactory.stringType),
+ method ->
+ EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(appView.options(), method));
+ }
+
+ public ProgramMethod ensureCompareToMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.enumMembers.compareTo.getName(),
+ dexItemFactory.createProto(
+ dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+ method -> EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(appView.options(), method));
+ }
+
+ public ProgramMethod ensureEqualsMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.enumMembers.equals.getName(),
+ dexItemFactory.createProto(
+ dexItemFactory.booleanType, dexItemFactory.intType, dexItemFactory.intType),
+ method -> EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), method));
+ }
+
+ public ProgramMethod ensureOrdinalMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.enumMembers.ordinalMethod.getName(),
+ dexItemFactory.createProto(dexItemFactory.intType, dexItemFactory.intType),
+ method -> EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), method));
+ }
+
+ private ProgramMethod internalEnsureMethod(
+ AppView<AppInfoWithLiveness> appView,
+ DexString methodName,
+ DexProto methodProto,
+ SyntheticCodeGenerator codeGenerator) {
// TODO(b/191957637): Consider creating free flowing static methods instead. The synthetic
// infrastructure needs to be augmented with a new method ensureFixedMethod() or
// ensureFixedFreeFlowingMethod() for this, if we want to create only one utility method (and
@@ -84,27 +141,17 @@
return appView
.getSyntheticItems()
.ensureFixedClassMethod(
- dexItemFactory.enumMembers.compareTo.getName(),
- dexItemFactory.createProto(
- dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+ methodName,
+ methodProto,
SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
- synthesizingContext,
+ getSynthesizingContext(),
appView,
ConsumerUtils.emptyConsumer(),
methodBuilder ->
methodBuilder
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
- .setCode(
- method ->
- EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
- appView.options(), method))
- .setClassFileVersion(CfVersion.V1_6),
- newMethod ->
- converter.processDesugaredMethod(
- newMethod,
- OptimizationFeedbackSimple.getInstance(),
- methodProcessor,
- methodProcessor.createMethodProcessingContext(newMethod)));
+ .setCode(codeGenerator)
+ .setClassFileVersion(CfVersion.V1_6));
}
@Override
@@ -146,8 +193,10 @@
SharedEnumUnboxingUtilityClass build() {
DexProgramClass clazz = createClass();
- return new SharedEnumUnboxingUtilityClass(
- clazz, synthesizingContext, new ProgramMethod(clazz, valuesMethod));
+ SharedEnumUnboxingUtilityClass sharedUtilityClass =
+ new SharedEnumUnboxingUtilityClass(
+ clazz, synthesizingContext, new ProgramMethod(clazz, valuesMethod));
+ return sharedUtilityClass;
}
private DexProgramClass createClass() {
@@ -166,7 +215,8 @@
ImmutableList.of(
createClassInitializer(sharedUtilityClassType, valuesField),
createValuesMethod(sharedUtilityClassType, valuesField)))
- .setStaticFields(ImmutableList.of(valuesField));
+ .setStaticFields(ImmutableList.of(valuesField))
+ .setUseSortedMethodBacking(true);
});
assert clazz.getAccessFlags().equals(ClassAccessFlags.createPublicFinalSynthetic());
return clazz;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..fd892c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
@@ -0,0 +1,42 @@
+// 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.optimize.enums.classification;
+
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
+
+public final class CheckNotNullEnumUnboxerMethodClassification
+ extends EnumUnboxerMethodClassification {
+
+ private int argumentIndex;
+
+ CheckNotNullEnumUnboxerMethodClassification(int argumentIndex) {
+ this.argumentIndex = argumentIndex;
+ }
+
+ public int getArgumentIndex() {
+ return argumentIndex;
+ }
+
+ public boolean isUseEligibleForUnboxing(InvokeStatic invoke, Value enumValue) {
+ for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+ Value argument = invoke.getArgument(argumentIndex);
+ if (argument == enumValue && argumentIndex != getArgumentIndex()) {
+ return false;
+ }
+ }
+ return invoke.hasUnusedOutValue();
+ }
+
+ @Override
+ public boolean isCheckNotNullClassification() {
+ return true;
+ }
+
+ @Override
+ public CheckNotNullEnumUnboxerMethodClassification asCheckNotNullClassification() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..2b34291
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
@@ -0,0 +1,30 @@
+// 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.optimize.enums.classification;
+
+public abstract class EnumUnboxerMethodClassification {
+
+ public static UnknownEnumUnboxerMethodClassification unknown() {
+ return UnknownEnumUnboxerMethodClassification.getInstance();
+ }
+
+ public EnumUnboxerMethodClassification fixupAfterRemovingThisParameter() {
+ // Only static methods are currently classified by the enum unboxer.
+ assert isUnknownClassification();
+ return unknown();
+ }
+
+ public boolean isCheckNotNullClassification() {
+ return false;
+ }
+
+ public CheckNotNullEnumUnboxerMethodClassification asCheckNotNullClassification() {
+ return null;
+ }
+
+ public boolean isUnknownClassification() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
new file mode 100644
index 0000000..f01abfc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
@@ -0,0 +1,106 @@
+// 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.optimize.enums.classification;
+
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Set;
+
+public class EnumUnboxerMethodClassificationAnalysis {
+
+ /**
+ * Simple analysis that classifies the given method using {@link
+ * CheckNotNullEnumUnboxerMethodClassification} if the method is static and has a parameter of
+ * type Object, which has a single if-zero user.
+ */
+ public static EnumUnboxerMethodClassification analyze(
+ AppView<AppInfoWithLiveness> appView,
+ ProgramMethod method,
+ IRCode code,
+ MethodProcessor methodProcessor) {
+ if (!appView.options().enableEnumUnboxing) {
+ // The classification is unused when enum unboxing is disabled.
+ return EnumUnboxerMethodClassification.unknown();
+ }
+
+ if (!method.getAccessFlags().isStatic() || method.getParameters().isEmpty()) {
+ // Not classified for enum unboxing.
+ return EnumUnboxerMethodClassification.unknown();
+ }
+
+ // Look for an argument with a single if-zero user.
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ InstructionIterator entryIterator = code.entryBlock().iterator();
+ for (int index = 0; index < method.getParameters().size(); index++) {
+ Argument argument = entryIterator.next().asArgument();
+ DexType parameter = method.getParameter(index);
+ if (parameter != dexItemFactory.objectType) {
+ continue;
+ }
+
+ if (onlyHasCheckNotNullUsers(argument, methodProcessor)) {
+ return new CheckNotNullEnumUnboxerMethodClassification(index);
+ }
+ }
+
+ return EnumUnboxerMethodClassification.unknown();
+ }
+
+ private static boolean onlyHasCheckNotNullUsers(
+ Argument argument, MethodProcessor methodProcessor) {
+ // Check that the argument has an if-zero user and a return user (optional).
+ Value value = argument.outValue();
+ if (value.hasDebugUsers() || value.hasPhiUsers()) {
+ return false;
+ }
+ Set<Instruction> users = value.aliasedUsers();
+ boolean seenIf = false;
+ for (Instruction user : users) {
+ switch (user.opcode()) {
+ case ASSUME:
+ if (user.outValue().hasDebugUsers() || user.outValue().hasPhiUsers()) {
+ return false;
+ }
+ break;
+ case IF:
+ {
+ If ifUser = user.asIf();
+ if (!ifUser.isZeroTest()
+ || (ifUser.getType() != Type.EQ && ifUser.getType() != Type.NE)) {
+ return false;
+ }
+ seenIf = true;
+ break;
+ }
+ case RETURN:
+ break;
+ default:
+ return false;
+ }
+ }
+
+ // During the primary optimization pass, we require seeing an if instruction (to limit the
+ // amount of method duplication). For monotonicity, we do not require seeing an if instruction
+ // in the subsequent optimization passes, since the if instruction that lead us to return a
+ // CheckNotNullEnumUnboxerMethodClassification may be eliminated by (for example) the call site
+ // optimization.
+ return !methodProcessor.isPrimaryMethodProcessor() || seenIf;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..9f51d0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
@@ -0,0 +1,22 @@
+// 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.optimize.enums.classification;
+
+public final class UnknownEnumUnboxerMethodClassification extends EnumUnboxerMethodClassification {
+
+ private static final UnknownEnumUnboxerMethodClassification INSTANCE =
+ new UnknownEnumUnboxerMethodClassification();
+
+ private UnknownEnumUnboxerMethodClassification() {}
+
+ static UnknownEnumUnboxerMethodClassification getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isUnknownClassification() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
new file mode 100644
index 0000000..c2c5427
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -0,0 +1,127 @@
+// 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.optimize.enums.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.IteratorUtils;
+
+/**
+ * A special code object used by enum unboxing that supplies IR from an existing method
+ * checkNotNull() method. Minor rewritings are applied to the code after IR building to account for
+ * enum unboxing.
+ *
+ * <p>Instances of {@link CheckNotZeroCode} are converted to {@link
+ * com.android.tools.r8.graph.CfCode} or {@link com.android.tools.r8.graph.DexCode} immediately, and
+ * thus should never be seen outside of the {@link
+ * com.android.tools.r8.ir.optimize.enums.EnumUnboxer}.
+ */
+public class CheckNotZeroCode extends Code {
+
+ // The checkNotNull() method to build IR from.
+ private final ProgramMethod checkNotNullMethod;
+
+ public CheckNotZeroCode(ProgramMethod checkNotNullMethod) {
+ this.checkNotNullMethod = checkNotNullMethod;
+ }
+
+ @Override
+ public IRCode buildIR(ProgramMethod checkNotZeroMethod, AppView<?> appView, Origin origin) {
+ // Build IR from the checkNotNull() method.
+ IRCode code = checkNotNullMethod.buildIR(appView);
+ InstructionListIterator instructionIterator = code.instructionListIterator();
+
+ // Start iterating at the argument instruction for the checked argument.
+ IteratorUtils.skip(
+ instructionIterator,
+ checkNotNullMethod
+ .getOptimizationInfo()
+ .getEnumUnboxerMethodClassification()
+ .asCheckNotNullClassification()
+ .getArgumentIndex());
+
+ // Rewrite the type of the argument instruction to int.
+ Argument argument = instructionIterator.next().asArgument();
+ instructionIterator.replaceCurrentInstruction(
+ Argument.builder()
+ .setFreshOutValue(code, TypeElement.getInt())
+ .setIndex(argument.getIndex())
+ .build());
+
+ // Remove any assume instructions linked to the argument and replace all returns by return-void.
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (instruction.isAssume()) {
+ instruction.outValue().replaceUsers(instruction.getFirstOperand());
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ } else if (instruction.isReturn() && instruction.asReturn().hasReturnValue()) {
+ instructionIterator.replaceCurrentInstruction(new Return());
+ }
+ }
+
+ // Transfer the IR to the given checkNotZero() method.
+ return new IRCode(
+ appView.options(),
+ checkNotZeroMethod,
+ code.getBlocks(),
+ code.valueNumberGenerator,
+ code.basicBlockNumberGenerator,
+ code.metadata(),
+ checkNotZeroMethod.getOrigin());
+ }
+
+ @Override
+ protected boolean computeEquals(Object other) {
+ throw new Unreachable();
+ }
+
+ @Override
+ protected int computeHashCode() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public int estimatedDexCodeSizeUpperBoundInBytes() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isEmptyVoidMethod() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public String toString() {
+ return "CheckNotZeroCode(" + checkNotNullMethod.toSourceString() + ")";
+ }
+
+ @Override
+ public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+ return toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index a98bc38..2d8ff77 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -65,6 +66,11 @@
}
@Override
+ public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
+ return EnumUnboxerMethodClassification.unknown();
+ }
+
+ @Override
public TypeElement getDynamicUpperBoundType() {
return UNKNOWN_TYPE;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index a25eabb..4de895c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.utils.InternalOptions;
@@ -36,6 +37,8 @@
public abstract ClassInlinerMethodConstraint getClassInlinerMethodConstraint();
+ public abstract EnumUnboxerMethodClassification getEnumUnboxerMethodClassification();
+
public abstract TypeElement getDynamicUpperBoundType();
public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 15afa8c..ccb3076 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -83,9 +83,12 @@
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ClassInlinerMethodConstraintAnalysis;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassificationAnalysis;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -129,6 +132,7 @@
OptimizationFeedback feedback,
DynamicTypeOptimization dynamicTypeOptimization,
InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
+ MethodProcessor methodProcessor,
Timing timing) {
DexEncodedMethod definition = method.getDefinition();
identifyBridgeInfo(definition, code, feedback, timing);
@@ -136,10 +140,15 @@
if (options.enableInlining) {
identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
}
- computeClassInlinerMethodConstraint(method, code, feedback, timing);
+ if (options.enableClassInlining) {
+ computeClassInlinerMethodConstraint(method, code, feedback, timing);
+ }
+ computeEnumUnboxerMethodClassification(method, code, feedback, methodProcessor, timing);
computeSimpleInliningConstraint(method, code, feedback, timing);
computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
- computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
+ if (options.enableInitializedClassesAnalysis) {
+ computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
+ }
computeInstanceInitializerInfo(
definition, code, feedback, instanceFieldInitializationInfos, timing);
computeMayHaveSideEffects(feedback, definition, code, timing);
@@ -783,6 +792,27 @@
feedback.setClassInlinerMethodConstraint(method, classInlinerMethodConstraint);
}
+ private void computeEnumUnboxerMethodClassification(
+ ProgramMethod method,
+ IRCode code,
+ OptimizationFeedback feedback,
+ MethodProcessor methodProcessor,
+ Timing timing) {
+ timing.begin("Compute enum unboxer method classification");
+ computeEnumUnboxerMethodClassification(method, code, feedback, methodProcessor);
+ timing.end();
+ }
+
+ private void computeEnumUnboxerMethodClassification(
+ ProgramMethod method,
+ IRCode code,
+ OptimizationFeedback feedback,
+ MethodProcessor methodProcessor) {
+ EnumUnboxerMethodClassification enumUnboxerMethodClassification =
+ EnumUnboxerMethodClassificationAnalysis.analyze(appView, method, code, methodProcessor);
+ feedback.setEnumUnboxerMethodClassification(method, enumUnboxerMethodClassification);
+ }
+
private void computeSimpleInliningConstraint(
ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
if (appView.options().enableSimpleInliningConstraints) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 7a0dae1..348312f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -41,6 +42,8 @@
DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
private ClassInlinerMethodConstraint classInlinerConstraint =
ClassInlinerMethodConstraint.alwaysFalse();
+ private EnumUnboxerMethodClassification enumUnboxerMethodClassification =
+ EnumUnboxerMethodClassification.unknown();
private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
private ClassTypeElement returnsObjectWithLowerBoundType =
DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
@@ -154,6 +157,7 @@
nonNullParamOrThrow = template.nonNullParamOrThrow;
nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
classInlinerConstraint = template.classInlinerConstraint;
+ enumUnboxerMethodClassification = template.enumUnboxerMethodClassification;
apiReferenceLevel = template.apiReferenceLevel;
}
@@ -246,6 +250,19 @@
}
@Override
+ public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
+ return enumUnboxerMethodClassification;
+ }
+
+ void setEnumUnboxerMethodClassification(
+ EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+ // Check monotonicity.
+ assert !this.enumUnboxerMethodClassification.isCheckNotNullClassification()
+ || enumUnboxerMethodClassification.isCheckNotNullClassification();
+ this.enumUnboxerMethodClassification = enumUnboxerMethodClassification;
+ }
+
+ @Override
public TypeElement getDynamicUpperBoundType() {
return returnsObjectWithUpperBoundType;
}
@@ -555,6 +572,8 @@
public void adjustOptimizationInfoAfterRemovingThisParameter(
AppView<AppInfoWithLiveness> appView) {
classInlinerConstraint = classInlinerConstraint.fixupAfterRemovingThisParameter();
+ enumUnboxerMethodClassification =
+ enumUnboxerMethodClassification.fixupAfterRemovingThisParameter();
simpleInliningConstraint =
simpleInliningConstraint.fixupAfterRemovingThisParameter(
appView.simpleInliningConstraintFactory());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index 89674f5..b38f436 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -36,6 +36,10 @@
}
}
+ public static OptimizationFeedbackSimple getSimple() {
+ return OptimizationFeedbackSimple.getInstance();
+ }
+
public void fixupOptimizationInfos(
AppView<?> appView, ExecutorService executorService, OptimizationInfoFixer fixer)
throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index dd781e8..c9390d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -264,6 +265,13 @@
}
@Override
+ public synchronized void setEnumUnboxerMethodClassification(
+ ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+ getMethodOptimizationInfoForUpdating(method)
+ .setEnumUnboxerMethodClassification(enumUnboxerMethodClassification);
+ }
+
+ @Override
public synchronized void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index e862824..623d8d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -117,6 +118,10 @@
ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {}
@Override
+ public void setEnumUnboxerMethodClassification(
+ ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {}
+
+ @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 5c39d40..b248a6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -169,6 +170,12 @@
}
@Override
+ public void setEnumUnboxerMethodClassification(
+ ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+ // Ignored.
+ }
+
+ @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
new file mode 100644
index 0000000..5fb3d05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
@@ -0,0 +1,58 @@
+// 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.optimize.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class ByteMethodOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+
+ ByteMethodOptimizer(AppView<?> appView) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedByteType;
+ }
+
+ @Override
+ public void optimize(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ Set<Value> affectedValues) {
+ if (singleTarget.getReference() == dexItemFactory.byteMembers.byteValue) {
+ optimizeByteValue(instructionIterator, invoke);
+ }
+ }
+
+ private void optimizeByteValue(
+ InstructionListIterator instructionIterator, InvokeMethod byteValueInvoke) {
+ // Optimize Byte.valueOf(b).byteValue() into b.
+ Value argument = byteValueInvoke.getFirstArgument().getAliasedValue();
+ if (!argument.isPhi()) {
+ Instruction definition = argument.getDefinition();
+ if (definition.isInvokeStatic()
+ && definition.asInvokeStatic().getInvokedMethod() == dexItemFactory.byteMembers.valueOf) {
+ byteValueInvoke.outValue().replaceUsers(definition.asInvokeStatic().getFirstArgument());
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index b7bc845..1ce2493 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -41,6 +41,7 @@
public LibraryMemberOptimizer(AppView<?> appView) {
this.appView = appView;
register(new BooleanMethodOptimizer(appView));
+ register(new ByteMethodOptimizer(appView));
register(new ObjectMethodOptimizer(appView));
register(new ObjectsMethodOptimizer(appView));
register(new StringBuilderMethodOptimizer(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index 0e8d33c..ab74fb6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -40,6 +40,9 @@
ImmutableMap.Builder<DexMethod, BiPredicate<DexMethod, List<Value>>> builder =
ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
.put(
+ dexItemFactory.byteMembers.byteValue,
+ (method, arguments) -> arguments.get(0).isNeverNull())
+ .put(
dexItemFactory.objectsMethods.toStringWithObject,
(method, arguments) ->
!JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
@@ -68,6 +71,7 @@
return ImmutableSet.<DexMethod>builder()
.add(dexItemFactory.booleanMembers.toString)
.add(dexItemFactory.byteMembers.toString)
+ .add(dexItemFactory.byteMembers.valueOf)
.add(dexItemFactory.classMethods.desiredAssertionStatus)
.add(dexItemFactory.charMembers.toString)
.add(dexItemFactory.doubleMembers.toString)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
index c5f6ddc..efc5228 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
@@ -22,7 +22,7 @@
@Override
protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
- if (methodMap.get(originalMethod) == newMethod) {
+ if (methodMap.apply(originalMethod) == newMethod) {
assert type == Type.VIRTUAL || type == Type.DIRECT;
return Type.STATIC;
}
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 179dffd..e98adec 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
@@ -2574,7 +2574,7 @@
addLiveRange(
definition,
block,
- instruction.getNumber() + INSTRUCTION_NUMBER_DELTA,
+ instruction.getNumber() + INSTRUCTION_NUMBER_DELTA - 1,
liveIntervals,
options);
assert !options.isGeneratingClassFiles() || instruction.isArgument()
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 9cb4c09..0c5705c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -416,6 +416,9 @@
register = NO_REGISTER;
return this;
}
+
+ assert !getValue().isDefinedByInstructionSatisfying(Instruction::isInitClass);
+
start = toGapPosition(start);
LiveIntervals splitChild = new LiveIntervals(splitParent);
splitParent.splitChildren.add(splitChild);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index db93ced..5680337 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -32,6 +32,8 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.collections.ImmutableDeque;
import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
import java.util.ArrayList;
@@ -62,22 +64,25 @@
public static class APIConverterVivifiedWrapperCfCodeProvider
extends DesugaredLibraryAPIConversionCfCodeProvider {
- DexField wrapperField;
- DexMethod forwardMethod;
- DesugaredLibraryAPIConverter converter;
- boolean itfCall;
+ private final DexField wrapperField;
+ private final DexMethod forwardMethod;
+ private final DesugaredLibraryAPIConverter converter;
+ private final boolean itfCall;
+ private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
public APIConverterVivifiedWrapperCfCodeProvider(
AppView<?> appView,
DexMethod forwardMethod,
DexField wrapperField,
DesugaredLibraryAPIConverter converter,
- boolean itfCall) {
+ boolean itfCall,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
super(appView, wrapperField.holder);
this.forwardMethod = forwardMethod;
this.wrapperField = wrapperField;
this.converter = converter;
this.itfCall = itfCall;
+ this.eventConsumer = eventConsumer;
}
@Override
@@ -98,7 +103,8 @@
instructions.add(
new CfInvoke(
Opcodes.INVOKESTATIC,
- converter.ensureConversionMethod(param, param, vivifiedTypeFor(param)),
+ converter.ensureConversionMethod(
+ param, param, vivifiedTypeFor(param), eventConsumer),
false));
newParameters[index - 1] = vivifiedTypeFor(param);
}
@@ -130,7 +136,7 @@
new CfInvoke(
Opcodes.INVOKESTATIC,
converter.ensureConversionMethod(
- returnType, vivifiedTypeFor(returnType), returnType),
+ returnType, vivifiedTypeFor(returnType), returnType, eventConsumer),
false));
}
if (returnType == factory.voidType) {
@@ -149,19 +155,22 @@
DexMethod forwardMethod;
DesugaredLibraryAPIConverter converter;
boolean itfCall;
+ private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
public APIConverterWrapperCfCodeProvider(
AppView<?> appView,
DexMethod forwardMethod,
DexField wrapperField,
DesugaredLibraryAPIConverter converter,
- boolean itfCall) {
+ boolean itfCall,
+ DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
// Var wrapperField is null if should forward to receiver.
super(appView, wrapperField == null ? forwardMethod.holder : wrapperField.holder);
this.forwardMethod = forwardMethod;
this.wrapperField = wrapperField;
this.converter = converter;
this.itfCall = itfCall;
+ this.eventConsumer = eventConsumer;
}
@Override
@@ -185,7 +194,8 @@
instructions.add(
new CfInvoke(
Opcodes.INVOKESTATIC,
- converter.ensureConversionMethod(param, vivifiedTypeFor(param), param),
+ converter.ensureConversionMethod(
+ param, vivifiedTypeFor(param), param, eventConsumer),
false));
}
if (param == factory.longType || param == factory.doubleType) {
@@ -206,7 +216,7 @@
new CfInvoke(
Opcodes.INVOKESTATIC,
converter.ensureConversionMethod(
- returnType, returnType, vivifiedTypeFor(returnType)),
+ returnType, returnType, vivifiedTypeFor(returnType), eventConsumer),
false));
returnType = vivifiedTypeFor(returnType);
}
@@ -282,6 +292,105 @@
}
}
+ public static class APIConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+ private final CfInvoke initialInvoke;
+ private final DexMethod returnConversion;
+ private final DexMethod[] parameterConversions;
+
+ public APIConversionCfCodeProvider(
+ AppView<?> appView,
+ DexType holder,
+ CfInvoke initialInvoke,
+ DexMethod returnConversion,
+ DexMethod[] parameterConversions) {
+ super(appView, holder);
+ this.initialInvoke = initialInvoke;
+ this.returnConversion = returnConversion;
+ this.parameterConversions = parameterConversions;
+ }
+
+ private DexType invalidType(DexMethod invokedMethod, DexMethod convertedMethod) {
+ if (invokedMethod.getReturnType() != convertedMethod.getReturnType()
+ && returnConversion == null) {
+ return invokedMethod.getReturnType();
+ }
+ for (int i = 0; i < invokedMethod.getArity(); i++) {
+ if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i)
+ && parameterConversions[i] == null) {
+ return invokedMethod.getParameter(i);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ DexMethod invokedMethod = initialInvoke.getMethod();
+ DexMethod convertedMethod =
+ DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+ invokedMethod, invokedMethod.holder, appView);
+
+ DexType invalidType = invalidType(invokedMethod, convertedMethod);
+ if (invalidType != null) {
+ // This is true if the API conversion requires to convert a type which is impossible to
+ // convert: no custom conversion and no wrapping possible. This is extremely rare and
+ // should happen only with broken desugared library set-ups. A warning has already been
+ // reported at this point.
+ DexString message =
+ appView
+ .dexItemFactory()
+ .createString(
+ "The method "
+ + invokedMethod
+ + " requires API conversion, but conversion was impossible because of the"
+ + " non convertible type "
+ + invalidType
+ + ".");
+ return new APIConverterThrowRuntimeExceptionCfCodeProvider(
+ appView, message, invokedMethod.getHolderType())
+ .generateCfCode();
+ }
+
+ List<CfInstruction> instructions = new ArrayList<>();
+
+ boolean isStatic = initialInvoke.getOpcode() == Opcodes.INVOKESTATIC;
+ if (!isStatic) {
+ instructions.add(new CfLoad(ValueType.fromDexType(invokedMethod.holder), 0));
+ }
+ int receiverShift = BooleanUtils.intValue(!isStatic);
+ int stackIndex = 0;
+ for (int i = 0; i < invokedMethod.getArity(); i++) {
+ DexType param = invokedMethod.getParameter(i);
+ instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex + receiverShift));
+ if (parameterConversions[i] != null) {
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+ }
+ if (param == appView.dexItemFactory().longType
+ || param == appView.dexItemFactory().doubleType) {
+ stackIndex++;
+ }
+ stackIndex++;
+ }
+
+ // Actual call to converted value.
+ instructions.add(
+ new CfInvoke(initialInvoke.getOpcode(), convertedMethod, initialInvoke.isInterface()));
+
+ // Return conversion.
+ if (returnConversion != null) {
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
+ }
+
+ if (invokedMethod.getReturnType().isVoidType()) {
+ instructions.add(new CfReturnVoid());
+ } else {
+ instructions.add(new CfReturn(ValueType.fromDexType(invokedMethod.getReturnType())));
+ }
+ return standardCfCodeFromInstructions(instructions);
+ }
+ }
+
public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
DexField wrapperField;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
index 7f26a19..e1ba3b2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
@@ -36,12 +36,13 @@
private final List<Pair<DexType, DexMethod>> extraDispatchCases;
public EmulateInterfaceSyntheticCfCodeProvider(
+ DexType holder,
DexType interfaceType,
DexMethod companionMethod,
DexMethod libraryMethod,
List<Pair<DexType, DexMethod>> extraDispatchCases,
AppView<?> appView) {
- super(appView, interfaceType);
+ super(appView, holder);
this.interfaceType = interfaceType;
this.companionMethod = companionMethod;
this.libraryMethod = libraryMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index d21c6d9..cb5de3e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
import com.android.tools.r8.utils.collections.ImmutableDeque;
import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
@@ -67,14 +68,19 @@
private final AbstractValue nullValue;
public EnumUnboxingInstanceFieldCfCodeProvider(
+ AppView<?> appView, DexType holder, EnumData data, DexField field) {
+ this(appView, holder, data, field, null);
+ }
+
+ public EnumUnboxingInstanceFieldCfCodeProvider(
AppView<?> appView,
DexType holder,
- DexType returnType,
- EnumInstanceFieldMappingData fieldDataMap,
+ EnumData data,
+ DexField field,
AbstractValue nullValue) {
super(appView, holder);
- this.returnType = returnType;
- this.fieldDataMap = fieldDataMap;
+ this.returnType = field.getType();
+ this.fieldDataMap = data.getInstanceFieldData(field).asEnumFieldMappingData();
this.nullValue = nullValue;
}
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 16b6508..b719c89 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -50,13 +51,15 @@
// ClassNameMinifier.
Predicate<DexType> wasPruned =
appView.hasLiveness() ? appView.withLiveness().appInfo()::wasPruned : alwaysFalse();
+ Predicate<DexType> hasGenericTypeVariables =
+ type -> GenericSignatureContextBuilder.hasGenericTypeVariables(appView, type, wasPruned);
BiPredicate<DexType, DexType> hasPrunedRelationship =
(enclosing, enclosed) ->
contextBuilder.hasPrunedRelationship(appView, enclosing, enclosed, wasPruned);
- Predicate<DexType> hasGenericTypeVariables =
- type -> contextBuilder.hasGenericTypeVariables(appView, type, wasPruned);
ThreadUtils.processItems(
- classes,
+ // Final merging of classes can introduce pruned types that still exists in classes, we
+ // therefore prune them from work here.
+ IterableUtils.filter(classes, clazz -> !wasPruned.test(clazz.getType())),
clazz -> {
GenericSignaturePartialTypeArgumentApplier classArgumentApplier =
contextBuilder != null
@@ -68,7 +71,7 @@
hasGenericTypeVariables)
: null;
GenericSignatureTypeRewriter genericSignatureTypeRewriter =
- new GenericSignatureTypeRewriter(appView, clazz);
+ new GenericSignatureTypeRewriter(appView, clazz, hasGenericTypeVariables);
clazz.setClassSignature(
genericSignatureTypeRewriter.rewrite(
classArgumentApplier != null
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
index 5b97077..c83cbfc 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
@@ -5,7 +5,10 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import com.android.tools.r8.graph.ProgramDefinition;
import java.util.List;
+import java.util.Objects;
public abstract class AnnotationMatchResult {
@@ -31,13 +34,13 @@
static class ConcreteAnnotationMatchResult extends AnnotationMatchResult {
- private final List<DexAnnotation> matchedAnnotations;
+ private final List<MatchedAnnotation> matchedAnnotations;
- public ConcreteAnnotationMatchResult(List<DexAnnotation> matchedAnnotations) {
+ public ConcreteAnnotationMatchResult(List<MatchedAnnotation> matchedAnnotations) {
this.matchedAnnotations = matchedAnnotations;
}
- public List<DexAnnotation> getMatchedAnnotations() {
+ public List<MatchedAnnotation> getMatchedAnnotations() {
return matchedAnnotations;
}
@@ -51,4 +54,49 @@
return this;
}
}
+
+ static class MatchedAnnotation {
+
+ private final ProgramDefinition annotatedItem;
+ private final DexAnnotation annotation;
+ private final AnnotatedKind annotatedKind;
+
+ MatchedAnnotation(
+ ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+ this.annotatedItem = annotatedItem;
+ this.annotation = annotation;
+ this.annotatedKind = annotatedKind;
+ }
+
+ public ProgramDefinition getAnnotatedItem() {
+ return annotatedItem;
+ }
+
+ public DexAnnotation getAnnotation() {
+ return annotation;
+ }
+
+ public AnnotatedKind getAnnotatedKind() {
+ return annotatedKind;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof MatchedAnnotation) {
+ MatchedAnnotation annotationToRetain = (MatchedAnnotation) obj;
+ return annotatedItem == annotationToRetain.annotatedItem
+ && annotation == annotationToRetain.annotation
+ && annotatedKind == annotationToRetain.annotatedKind;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(annotatedItem, annotation, annotatedKind);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 6a4089d..ace24131 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
import com.android.tools.r8.kotlin.KotlinPropertyInfo;
+import com.android.tools.r8.shaking.Enqueuer.Mode;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableList;
@@ -33,6 +34,7 @@
public class AnnotationRemover {
private final AppView<AppInfoWithLiveness> appView;
+ private final Mode mode;
private final InternalOptions options;
private final Set<DexAnnotation> annotationsToRetain;
private final ProguardKeepAttributes keep;
@@ -41,16 +43,18 @@
private AnnotationRemover(
AppView<AppInfoWithLiveness> appView,
Set<DexAnnotation> annotationsToRetain,
+ Mode mode,
Set<DexType> removedClasses) {
this.appView = appView;
+ this.mode = mode;
this.options = appView.options();
this.annotationsToRetain = annotationsToRetain;
this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
this.removedClasses = removedClasses;
}
- public static Builder builder() {
- return new Builder();
+ public static Builder builder(Mode mode) {
+ return new Builder(mode);
}
/** Used to filter annotations on classes, methods and fields. */
@@ -263,17 +267,24 @@
}
private void removeAnnotations(ProgramDefinition definition, KeepInfo<?, ?> keepInfo) {
+ assert mode.isInitialTreeShaking() || annotationsToRetain.isEmpty();
boolean isAnnotation =
definition.isProgramClass() && definition.asProgramClass().isAnnotation();
- if ((options.isForceProguardCompatibilityEnabled() || keepInfo.isPinned())) {
- definition.rewriteAllAnnotations(
- (annotation, kind) -> rewriteAnnotation(definition, annotation, kind));
- } else if (!isAnnotation) {
- definition.clearAllAnnotations();
+ if (keepInfo.isAnnotationRemovalAllowed(options)) {
+ if (isAnnotation) {
+ definition.rewriteAllAnnotations(
+ (annotation, isParameterAnnotation) ->
+ shouldRetainAnnotationOnAnnotationClass(annotation) ? annotation : null);
+ } else if (mode.isInitialTreeShaking()) {
+ definition.rewriteAllAnnotations(
+ (annotation, isParameterAnnotation) ->
+ annotationsToRetain.contains(annotation) ? annotation : null);
+ } else {
+ definition.clearAllAnnotations();
+ }
} else {
definition.rewriteAllAnnotations(
- (annotation, isParameterAnnotation) ->
- shouldRetainAnnotationOnAnnotationClass(annotation) ? annotation : null);
+ (annotation, kind) -> rewriteAnnotation(definition, annotation, kind));
}
}
@@ -284,7 +295,7 @@
if (DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())) {
return shouldRetainRetentionAnnotationOnAnnotationClass(annotation);
}
- return false;
+ return annotationsToRetain.contains(annotation);
}
private boolean shouldRetainAnnotationDefaultAnnotationOnAnnotationClass(
@@ -387,13 +398,23 @@
*/
private final Set<DexAnnotation> annotationsToRetain = Sets.newIdentityHashSet();
+ private final Mode mode;
+
+ Builder(Mode mode) {
+ this.mode = mode;
+ }
+
+ public boolean isRetainedForFinalTreeShaking(DexAnnotation annotation) {
+ return annotationsToRetain.contains(annotation);
+ }
+
public void retainAnnotation(DexAnnotation annotation) {
annotationsToRetain.add(annotation);
}
public AnnotationRemover build(
AppView<AppInfoWithLiveness> appView, Set<DexType> removedClasses) {
- return new AnnotationRemover(appView, annotationsToRetain, removedClasses);
+ return new AnnotationRemover(appView, annotationsToRetain, mode, removedClasses);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 2ff9008..68b7e13 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1050,7 +1050,8 @@
.collect(Collectors.toList()));
CommittedItems committedItems = getSyntheticItems().commitRewrittenWithLens(application, lens);
- DexDefinitionSupplier definitionSupplier = application.getDefinitionsSupplier(committedItems);
+ DexDefinitionSupplier definitionSupplier =
+ committedItems.getApplication().getDefinitionsSupplier(committedItems);
return new AppInfoWithLiveness(
committedItems,
getClassToFeatureSplitMap().rewrittenWithLens(lens),
@@ -1472,4 +1473,13 @@
public boolean isNoVerticalClassMergingOfType(DexType type) {
return noClassMerging.contains(type) || noVerticalClassMerging.contains(type);
}
+
+ public boolean verifyNoIteratingOverPrunedClasses() {
+ classes()
+ .forEach(
+ clazz -> {
+ assert !wasPruned(clazz.type) : clazz.type + " was not pruned";
+ });
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 9537206..02aea63 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,8 +10,8 @@
import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.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.shaking.AnnotationRemover.shouldKeepAnnotation;
import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static java.util.Collections.emptySet;
import com.android.tools.r8.Diagnostic;
@@ -110,15 +110,17 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringTypeLookupResult;
+import com.android.tools.r8.shaking.AnnotationMatchResult.MatchedAnnotation;
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.shaking.EnqueuerEvent.ClassEnqueuerEvent;
+import com.android.tools.r8.shaking.EnqueuerEvent.InstantiatedClassEnqueuerEvent;
+import com.android.tools.r8.shaking.EnqueuerEvent.LiveClassEnqueuerEvent;
import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
-import com.android.tools.r8.shaking.KeepInfo.Joiner;
import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
import com.android.tools.r8.shaking.RootSetUtils.ItemsWithRules;
-import com.android.tools.r8.shaking.RootSetUtils.MutableItemsWithRules;
import com.android.tools.r8.shaking.RootSetUtils.RootSet;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
@@ -135,7 +137,9 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.Visibility;
import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.ProgramFieldMap;
import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableSet;
@@ -283,6 +287,10 @@
*/
private final SetWithReportedReason<DexProgramClass> liveTypes = new SetWithReportedReason<>();
+ /** Set of interfaces that have been transitioned to being instantiated indirectly. */
+ private final Set<DexProgramClass> interfacesTransitionedToInstantiated =
+ Sets.newIdentityHashSet();
+
/** Set of classes whose initializer may execute. */
private final SetWithReportedReason<DexProgramClass> initializedClasses =
new SetWithReportedReason<>();
@@ -378,6 +386,30 @@
private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
/**
+ * The minimum keep info for classes, fields, and methods. The minimum keep info for a program
+ * item should be applied to the {@link KeepInfo} of that item when the item becomes live.
+ */
+ private final Map<DexProgramClass, KeepClassInfo.Joiner> minimumKeepClassInfo =
+ new IdentityHashMap<>();
+
+ private final ProgramFieldMap<KeepFieldInfo.Joiner> minimumKeepFieldInfo =
+ ProgramFieldMap.create();
+ private final ProgramMethodMap<KeepMethodInfo.Joiner> minimumKeepMethodInfo =
+ ProgramMethodMap.create();
+
+ /**
+ * Conditional minimum keep info for classes, fields, and methods, which should only be applied if
+ * the outermost {@link EnqueuerEvent} is triggered during tracing (e.g., class X becomes live).
+ */
+ private final Map<EnqueuerEvent, Map<DexProgramClass, KeepClassInfo.Joiner>>
+ dependentMinimumKeepClassInfo = new HashMap<>();
+
+ private final Map<EnqueuerEvent, ProgramFieldMap<KeepFieldInfo.Joiner>>
+ dependentMinimumKeepFieldInfo = new HashMap<>();
+ private final Map<EnqueuerEvent, ProgramMethodMap<KeepMethodInfo.Joiner>>
+ dependentMinimumKeepMethodInfo = new HashMap<>();
+
+ /**
* A set of seen const-class references that serve as an initial lock-candidate set and will
* prevent class merging.
*/
@@ -863,10 +895,6 @@
method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition()));
}
- private void enqueueRootItem(ProgramDefinition item, Set<ProguardKeepRuleBase> rules) {
- internalEnqueueRootItem(item, rules, null);
- }
-
private void internalEnqueueRootItem(
ProgramDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
if (item.isProgramClass()) {
@@ -1830,7 +1858,9 @@
enqueueFirstNonSerializableClassInitializer(clazz, reason);
}
- checkDefinitionForSoftPinning(clazz);
+ // Update keep info.
+ applyMinimumKeepInfo(clazz);
+ applyMinimumKeepInfoDependentOn(new LiveClassEnqueuerEvent(clazz));
processAnnotations(clazz);
@@ -1968,13 +1998,13 @@
}
}
- private void processAnnotation(
+ void processAnnotation(
ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind kind) {
DexType type = annotation.getAnnotationType();
DexClass clazz = definitionFor(type, annotatedItem);
boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
- if (!shouldKeepAnnotation(appView, annotatedItem, annotation, isLive, kind)) {
+ if (!shouldKeepAnnotation(annotatedItem, annotation, kind, isLive)) {
// Remember this annotation for later.
if (!annotationTypeIsLibraryClass) {
Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
@@ -1998,6 +2028,20 @@
annotation.annotation.collectIndexedItems(referenceMarker);
}
+ private boolean shouldKeepAnnotation(
+ ProgramDefinition annotatedItem,
+ DexAnnotation annotation,
+ AnnotatedKind annotatedKind,
+ boolean isLive) {
+ if (annotationRemoverBuilder != null
+ && annotationRemoverBuilder.isRetainedForFinalTreeShaking(annotation)) {
+ assert mode.isInitialTreeShaking();
+ return true;
+ }
+ return AnnotationRemover.shouldKeepAnnotation(
+ appView, annotatedItem, annotation, isLive, annotatedKind);
+ }
+
private FieldResolutionResult resolveField(DexField field, ProgramDefinition context) {
// Record the references in case they are not program types.
FieldResolutionResult resolutionResult = appInfo.resolveField(field);
@@ -2608,16 +2652,46 @@
}
private void transitionDependentItemsForInstantiatedItem(DexProgramClass clazz) {
- do {
- // Handle keep rules that are dependent on the class being instantiated.
- rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentMember);
+ WorkList<DexProgramClass> interfacesToTransition =
+ WorkList.newWorkList(interfacesTransitionedToInstantiated);
+ if (clazz.getAccessFlags().isInterface()) {
+ interfacesToTransition.addIfNotSeen(clazz);
+ } else {
+ do {
+ // Handle keep rules that are dependent on the class being instantiated.
+ rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentMember);
+ applyMinimumKeepInfoDependentOn(new InstantiatedClassEnqueuerEvent(clazz));
- // Visit the super type.
- clazz =
- clazz.superType != null
- ? asProgramClassOrNull(appView.definitionFor(clazz.superType))
- : null;
- } while (clazz != null && !objectAllocationInfoCollection.isInstantiatedDirectly(clazz));
+ for (DexType interfaceType : clazz.getInterfaces()) {
+ DexProgramClass interfaceClass =
+ asProgramClassOrNull(definitionFor(interfaceType, clazz));
+ if (interfaceClass != null) {
+ interfacesToTransition.addIfNotSeen(interfaceClass);
+ }
+ }
+
+ // Visit the super type.
+ clazz =
+ clazz.superType != null
+ ? asProgramClassOrNull(appView.definitionFor(clazz.superType))
+ : null;
+ } while (clazz != null && !objectAllocationInfoCollection.isInstantiatedDirectly(clazz));
+ }
+
+ while (interfacesToTransition.hasNext()) {
+ DexProgramClass interfaceClass = interfacesToTransition.next();
+ rootSet.forEachDependentNonStaticMember(
+ interfaceClass, appView, this::enqueueDependentMember);
+ applyMinimumKeepInfoDependentOn(new InstantiatedClassEnqueuerEvent(interfaceClass));
+
+ for (DexType indirectInterfaceType : interfaceClass.getInterfaces()) {
+ DexProgramClass indirectInterfaceClass =
+ asProgramClassOrNull(definitionFor(indirectInterfaceType, interfaceClass));
+ if (indirectInterfaceClass != null) {
+ interfacesToTransition.addIfNotSeen(indirectInterfaceClass);
+ }
+ }
+ }
}
private void transitionUnusedInterfaceToLive(DexProgramClass clazz) {
@@ -2656,11 +2730,12 @@
traceFieldDefinition(field);
}
+ // Update keep info.
+ applyMinimumKeepInfo(field);
+
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(field.getDefinition()));
- checkDefinitionForSoftPinning(field);
-
// Notify analyses.
analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
}
@@ -2759,6 +2834,27 @@
: info.isWritten();
}
+ public boolean isPreconditionForMinimumKeepInfoSatisfied(EnqueuerEvent preconditionEvent) {
+ if (preconditionEvent == null) {
+ return true;
+ }
+ if (preconditionEvent.isClassEvent()) {
+ ClassEnqueuerEvent classEvent = preconditionEvent.asClassEvent();
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(classEvent.getType()));
+ if (clazz == null) {
+ return false;
+ }
+ if (preconditionEvent.isLiveClassEvent()) {
+ return liveTypes.contains(clazz);
+ }
+ if (preconditionEvent.isInstantiatedClassEvent()) {
+ return objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(clazz);
+ }
+ }
+ assert false;
+ return false;
+ }
+
public boolean isMemberLive(DexEncodedMember<?, ?> member) {
assert member != null;
return member.isDexEncodedField()
@@ -2794,7 +2890,7 @@
}
public void forAllLiveClasses(Consumer<DexProgramClass> consumer) {
- liveTypes.items.forEach(consumer);
+ liveTypes.getItems().forEach(consumer);
}
private void markVirtualMethodAsReachable(
@@ -3032,24 +3128,26 @@
if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
registerAnalysis(new ApiModelAnalysis(appView, apiReferenceLevelCache));
}
+
+ // Transfer the minimum keep info from the root set into the Enqueuer state.
+ rootSet.forEachMinimumKeepInfo(
+ appView,
+ this::recordDependentMinimumKeepInfo,
+ this::recordDependentMinimumKeepInfo,
+ this::recordDependentMinimumKeepInfo);
+
if (mode.isInitialTreeShaking()) {
// This is simulating the effect of the "root set" applied rules.
// This is done only in the initial pass, in subsequent passes the "rules" are reapplied
// by iterating the instances.
- assert appView.options().isMinificationEnabled() || rootSet.noObfuscation.isEmpty();
- for (DexReference reference : rootSet.noObfuscation) {
- keepInfo.evaluateRule(reference, appInfo, Joiner::disallowMinification);
- }
} else if (appView.getKeepInfo() != null) {
appView
.getKeepInfo()
- .getRuleInstances()
- .forEach(
- (reference, rules) -> {
- for (Consumer<Joiner<?, ?, ?>> rule : rules) {
- keepInfo.evaluateRule(reference, appInfo, rule);
- }
- });
+ .forEachRuleInstance(
+ appView,
+ this::applyMinimumKeepInfoWhenLive,
+ this::applyMinimumKeepInfoWhenLive,
+ this::applyMinimumKeepInfoWhenLiveOrTargeted);
}
if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
enqueueRootItems(rootSet.noShrinking);
@@ -3087,18 +3185,133 @@
return createEnqueuerResult(appInfo);
}
+ private void applyMinimumKeepInfo(DexProgramClass clazz) {
+ KeepClassInfo.Joiner minimumKeepInfoForClass = minimumKeepClassInfo.remove(clazz);
+ if (minimumKeepInfoForClass != null) {
+ keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfoForClass));
+ }
+ }
+
+ private void applyMinimumKeepInfoWhenLive(
+ DexProgramClass clazz, KeepClassInfo.Joiner minimumKeepInfo) {
+ if (liveTypes.contains(clazz)) {
+ keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfo));
+ } else {
+ minimumKeepClassInfo
+ .computeIfAbsent(clazz, ignoreKey(KeepClassInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ private void recordDependentMinimumKeepInfo(
+ EnqueuerEvent preconditionEvent,
+ DexProgramClass clazz,
+ KeepClassInfo.Joiner minimumKeepInfo) {
+ if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
+ applyMinimumKeepInfoWhenLive(clazz, minimumKeepInfo);
+ } else {
+ dependentMinimumKeepClassInfo
+ .computeIfAbsent(preconditionEvent, ignoreKey(IdentityHashMap::new))
+ .computeIfAbsent(clazz, ignoreKey(KeepClassInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ private void applyMinimumKeepInfo(ProgramField field) {
+ KeepFieldInfo.Joiner minimumKeepInfoForField = minimumKeepFieldInfo.remove(field);
+ if (minimumKeepInfoForField != null) {
+ keepInfo.joinField(field, info -> info.merge(minimumKeepInfoForField));
+ }
+ }
+
+ private void applyMinimumKeepInfoWhenLive(
+ ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
+ if (liveFields.contains(field)) {
+ keepInfo.joinField(field, info -> info.merge(minimumKeepInfo));
+ } else {
+ minimumKeepFieldInfo
+ .computeIfAbsent(field, ignoreKey(KeepFieldInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ private void recordDependentMinimumKeepInfo(
+ EnqueuerEvent preconditionEvent, ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
+ if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
+ applyMinimumKeepInfoWhenLive(field, minimumKeepInfo);
+ } else {
+ dependentMinimumKeepFieldInfo
+ .computeIfAbsent(preconditionEvent, ignoreKey(ProgramFieldMap::create))
+ .computeIfAbsent(field, ignoreKey(KeepFieldInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ private void applyMinimumKeepInfo(ProgramMethod method) {
+ KeepMethodInfo.Joiner minimumKeepInfoForMethod = minimumKeepMethodInfo.remove(method);
+ if (minimumKeepInfoForMethod != null) {
+ keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfoForMethod));
+ }
+ }
+
+ private void applyMinimumKeepInfoWhenLiveOrTargeted(
+ ProgramMethod method, KeepMethodInfo.Joiner minimumKeepInfo) {
+ if (liveMethods.contains(method) || targetedMethods.contains(method)) {
+ keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfo));
+ } else {
+ minimumKeepMethodInfo
+ .computeIfAbsent(method, ignoreKey(KeepMethodInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ private void recordDependentMinimumKeepInfo(
+ EnqueuerEvent preconditionEvent,
+ ProgramMethod method,
+ KeepMethodInfo.Joiner minimumKeepInfo) {
+ if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
+ applyMinimumKeepInfoWhenLiveOrTargeted(method, minimumKeepInfo);
+ } else {
+ dependentMinimumKeepMethodInfo
+ .computeIfAbsent(preconditionEvent, ignoreKey(ProgramMethodMap::create))
+ .computeIfAbsent(method, ignoreKey(KeepMethodInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ private void applyMinimumKeepInfoDependentOn(EnqueuerEvent precondition) {
+ Map<DexProgramClass, KeepClassInfo.Joiner> minimumKeepClassInfoDependentOnPrecondition =
+ dependentMinimumKeepClassInfo.remove(precondition);
+ if (minimumKeepClassInfoDependentOnPrecondition != null) {
+ minimumKeepClassInfoDependentOnPrecondition.forEach(this::applyMinimumKeepInfoWhenLive);
+ }
+
+ ProgramFieldMap<KeepFieldInfo.Joiner> minimumKeepFieldInfoDependentOnPrecondition =
+ dependentMinimumKeepFieldInfo.remove(precondition);
+ if (minimumKeepFieldInfoDependentOnPrecondition != null) {
+ minimumKeepFieldInfoDependentOnPrecondition.forEach(this::applyMinimumKeepInfoWhenLive);
+ }
+
+ ProgramMethodMap<KeepMethodInfo.Joiner> minimumKeepMethodInfoDependentOnPrecondition =
+ dependentMinimumKeepMethodInfo.remove(precondition);
+ if (minimumKeepMethodInfoDependentOnPrecondition != null) {
+ minimumKeepMethodInfoDependentOnPrecondition.forEach(
+ this::applyMinimumKeepInfoWhenLiveOrTargeted);
+ }
+ }
+
private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
keepInfo.joinClass(clazz, info -> applyKeepRules(clazz, rules, info));
}
- private void keepMethodWithRules(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
- keepInfo.joinMethod(method, info -> applyKeepRules(method, rules, info));
- }
-
private void keepFieldWithRules(ProgramField field, Set<ProguardKeepRuleBase> rules) {
keepInfo.joinField(field, info -> applyKeepRules(field, rules, info));
}
+ private void keepMethodWithRules(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
+ keepInfo.joinMethod(method, info -> applyKeepRules(method, rules, info));
+ }
+
private void applyKeepRules(
ProgramDefinition definition,
Set<ProguardKeepRuleBase> rules,
@@ -3114,12 +3327,15 @@
joiner.requireAccessModificationForRepackaging();
}
}
- if (!modifiers.allowsObfuscation) {
- joiner.disallowMinification();
- }
if (!modifiers.allowsAccessModification) {
joiner.disallowAccessModification();
}
+ if (!modifiers.allowsAnnotationRemoval) {
+ joiner.disallowAnnotationRemoval();
+ }
+ if (!modifiers.allowsObfuscation) {
+ joiner.disallowMinification();
+ }
}
}
@@ -3129,17 +3345,19 @@
private Map<DexMethod, MethodProcessingContext> methodProcessingContexts =
new ConcurrentHashMap<>();
- List<ProgramMethod> desugaredMethods = new LinkedList<>();
+ private final List<ProgramMethod> desugaredMethods = new LinkedList<>();
- Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
+ private final Map<DexMethod, ProgramMethod> liveMethods = new ConcurrentHashMap<>();
- Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
+ private final Map<DexType, DexClasspathClass> syntheticClasspathClasses =
+ new ConcurrentHashMap<>();
- Map<DexProgramClass, List<DexClass>> injectedInterfaces = new IdentityHashMap<>();
+ private final Map<DexProgramClass, Set<DexClass>> injectedInterfaces =
+ new ConcurrentHashMap<>();
// Subset of live methods that need have keep requirements.
- List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions =
- new ArrayList<>();
+ private final List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>>
+ liveMethodsWithKeepActions = new ArrayList<>();
SyntheticAdditions(ProcessorContext processorContext) {
this.processorContext = processorContext;
@@ -3154,7 +3372,8 @@
boolean empty =
desugaredMethods.isEmpty()
&& liveMethods.isEmpty()
- && syntheticClasspathClasses.isEmpty();
+ && syntheticClasspathClasses.isEmpty()
+ && injectedInterfaces.isEmpty();
assert !empty || liveMethodsWithKeepActions.isEmpty();
return empty;
}
@@ -3164,6 +3383,10 @@
assert old == null;
}
+ public void addLiveMethods(Iterable<ProgramMethod> methods) {
+ methods.forEach(this::addLiveMethod);
+ }
+
public void addLiveMethod(ProgramMethod method) {
DexMethod signature = method.getDefinition().getReference();
assert !liveMethods.containsKey(signature);
@@ -3171,8 +3394,8 @@
}
public void injectInterface(DexProgramClass clazz, DexClass newInterface) {
- List<DexClass> newInterfaces =
- injectedInterfaces.computeIfAbsent(clazz, ignored -> new ArrayList<>());
+ Set<DexClass> newInterfaces =
+ injectedInterfaces.computeIfAbsent(clazz, ignored -> Sets.newConcurrentHashSet());
newInterfaces.add(newInterface);
}
@@ -3535,7 +3758,7 @@
// Generate first the callbacks since they may require extra wrappers.
ProgramMethodSet callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
- callbacks.forEach(additions::addLiveMethod);
+ additions.addLiveMethods(callbacks);
// Generate wrappers on classpath so types are defined.
desugaredLibraryWrapperAnalysis.generateWrappers(additions::addLiveClasspathClass);
@@ -3645,8 +3868,8 @@
Set<DexEncodedMethod> reachableNotLive = Sets.difference(allLive, liveMethods.getItems());
Log.debug(getClass(), "%s methods are reachable but not live", reachableNotLive.size());
Log.info(getClass(), "Only reachable: %s", reachableNotLive);
- SetView<DexEncodedMethod> targetedButNotLive = Sets
- .difference(targetedMethods.getItems(), liveMethods.getItems());
+ SetView<DexEncodedMethod> targetedButNotLive =
+ Sets.difference(targetedMethods.getItems(), liveMethods.getItems());
Log.debug(getClass(), "%s methods are targeted but not live", targetedButNotLive.size());
Log.info(getClass(), "Targeted but not live: %s", targetedButNotLive);
}
@@ -3655,15 +3878,14 @@
}
}
- private void postProcessingDesugaring() {
+ private void postProcessingDesugaring() throws ExecutionException {
SyntheticAdditions syntheticAdditions =
new SyntheticAdditions(appView.createProcessorContext());
R8PostProcessingDesugaringEventConsumer eventConsumer =
- CfPostProcessingDesugaringEventConsumer.createForR8(
- appView, syntheticAdditions::addLiveMethod, syntheticAdditions);
- CfPostProcessingDesugaringCollection.create(appView, desugaring.getRetargetingInfo())
- .postProcessingDesugaring(eventConsumer);
+ CfPostProcessingDesugaringEventConsumer.createForR8(appView, syntheticAdditions);
+ CfPostProcessingDesugaringCollection.create(appView, null, desugaring.getRetargetingInfo())
+ .postProcessingDesugaring(eventConsumer, executorService);
if (syntheticAdditions.isEmpty()) {
return;
@@ -3687,7 +3909,7 @@
}
private long getNumberOfLiveItems() {
- long result = liveTypes.items.size();
+ long result = liveTypes.getItems().size();
result += liveMethods.items.size();
result += liveFields.fields.size();
return result;
@@ -3718,28 +3940,18 @@
enqueueRootItems(dependentItems);
}
});
- consequentRootSet.dependentSoftPinned.forEach(
- (reference, dependentItems) -> {
- if (isLiveProgramReference(reference)) {
- dependentItems.forEachReference(
- item -> {
- if (isLiveProgramReference(item)) {
- keepInfo.joinInfo(item, appView, Joiner::pin);
- }
- });
- }
- });
// TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking);
- if (mode.isInitialTreeShaking()) {
- for (DexReference reference : consequentRootSet.noObfuscation) {
- keepInfo.evaluateRule(reference, appView, Joiner::disallowMinification);
- }
- consequentRootSet.softPinned.forEachReference(
- reference -> keepInfo.evaluateRule(reference, appView, Joiner::pin));
- }
+
enqueueRootItems(consequentRootSet.noShrinking);
+
+ consequentRootSet.forEachMinimumKeepInfo(
+ appView,
+ this::recordDependentMinimumKeepInfo,
+ this::recordDependentMinimumKeepInfo,
+ this::recordDependentMinimumKeepInfo);
+
// Check for compatibility rules indicating that the holder must be implicitly kept.
if (forceProguardCompatibility) {
consequentRootSet.dependentKeepClassCompatRule.forEach(
@@ -3752,19 +3964,6 @@
}
}
- private boolean isLiveProgramReference(DexReference reference) {
- if (reference.isDexType()) {
- DexProgramClass clazz =
- DexProgramClass.asProgramClassOrNull(appInfo().definitionFor(reference.asDexType()));
- return clazz != null && isTypeLive(clazz);
- }
- DexMember<?, ?> member = reference.asDexMember();
- DexProgramClass holder =
- DexProgramClass.asProgramClassOrNull(appInfo().definitionFor(member.holder));
- ProgramMember<?, ?> programMember = member.lookupOnProgramClass(holder);
- return programMember != null && isMemberLive(programMember.getDefinition());
- }
-
private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
RootSetBuilder builder = RootSet.builder(appView, subtypingInfo);
for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
@@ -3810,10 +4009,16 @@
action.getAction().accept(builder);
}
- void retainAnnotationForFinalTreeShaking(List<DexAnnotation> annotations) {
+ void retainAnnotationForFinalTreeShaking(List<MatchedAnnotation> matchedAnnotations) {
assert mode.isInitialTreeShaking();
if (annotationRemoverBuilder != null) {
- annotations.forEach(annotationRemoverBuilder::retainAnnotation);
+ for (MatchedAnnotation matchedAnnotation : matchedAnnotations) {
+ annotationRemoverBuilder.retainAnnotation(matchedAnnotation.getAnnotation());
+ workList.enqueueTraceAnnotationAction(
+ matchedAnnotation.getAnnotatedItem(),
+ matchedAnnotation.getAnnotation(),
+ matchedAnnotation.getAnnotatedKind());
+ }
}
}
@@ -3979,7 +4184,6 @@
}
void traceMethodDefinitionExcludingCode(ProgramMethod method) {
- checkDefinitionForSoftPinning(method);
markReferencedTypesAsLive(method);
processAnnotations(method);
method
@@ -3987,6 +4191,9 @@
.getParameterAnnotations()
.forEachAnnotation(
annotation -> processAnnotation(method, annotation, AnnotatedKind.PARAMETER));
+
+ // Update keep info.
+ applyMinimumKeepInfo(method);
}
private void traceNonDesugaredCode(ProgramMethod method) {
@@ -4006,22 +4213,6 @@
analyses.forEach(analysis -> analysis.processTracedCode(method, registry));
}
- private void checkDefinitionForSoftPinning(ProgramDefinition definition) {
- DexReference reference = definition.getReference();
- Set<ProguardKeepRuleBase> softPinRules = rootSet.softPinned.getRulesForReference(reference);
- if (softPinRules != null) {
- assert softPinRules.stream().noneMatch(r -> r.getModifiers().allowsOptimization);
- keepInfo.joinInfo(reference, appInfo, Joiner::pin);
- }
- // Identify dependent soft pinning.
- MutableItemsWithRules items = rootSet.dependentSoftPinned.get(definition.getContextType());
- if (items != null && items.containsReference(reference)) {
- assert items.getRulesForReference(reference).stream()
- .noneMatch(r -> r.getModifiers().allowsOptimization);
- keepInfo.joinInfo(reference, appInfo, Joiner::pin);
- }
- }
-
private void markReferencedTypesAsLive(ProgramMethod method) {
markTypeAsLive(method.getHolder(), method);
markParameterAndReturnTypesAsLive(method);
@@ -4538,30 +4729,6 @@
}
}
- private static class SetWithReason<T> {
-
- private final Set<T> items = Sets.newIdentityHashSet();
-
- private final BiConsumer<T, KeepReason> register;
-
- public SetWithReason(BiConsumer<T, KeepReason> register) {
- this.register = register;
- }
-
- boolean add(T item, KeepReason reason) {
- register.accept(item, reason);
- return items.add(item);
- }
-
- boolean contains(T item) {
- return items.contains(item);
- }
-
- Set<T> getItems() {
- return Collections.unmodifiableSet(items);
- }
- }
-
private class AnnotationReferenceMarker implements IndexedItemCollection {
private final ProgramDefinition context;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
new file mode 100644
index 0000000..6f68a59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.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.shaking;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+
+public abstract class EnqueuerEvent {
+
+ public boolean isClassEvent() {
+ return false;
+ }
+
+ public ClassEnqueuerEvent asClassEvent() {
+ return null;
+ }
+
+ public boolean isLiveClassEvent() {
+ return false;
+ }
+
+ public LiveClassEnqueuerEvent asLiveClassEvent() {
+ return null;
+ }
+
+ public boolean isInstantiatedClassEvent() {
+ return false;
+ }
+
+ public InstantiatedClassEnqueuerEvent asInstantiatedClassEvent() {
+ return null;
+ }
+
+ public abstract static class ClassEnqueuerEvent extends EnqueuerEvent {
+
+ private final DexType clazz;
+
+ public ClassEnqueuerEvent(DexProgramClass clazz) {
+ this.clazz = clazz.getType();
+ }
+
+ public DexType getType() {
+ return clazz;
+ }
+
+ @Override
+ public boolean isClassEvent() {
+ return true;
+ }
+
+ @Override
+ public ClassEnqueuerEvent asClassEvent() {
+ return this;
+ }
+ }
+
+ public static class LiveClassEnqueuerEvent extends ClassEnqueuerEvent {
+
+ public LiveClassEnqueuerEvent(DexProgramClass clazz) {
+ super(clazz);
+ }
+
+ @Override
+ public boolean isLiveClassEvent() {
+ return true;
+ }
+
+ @Override
+ public LiveClassEnqueuerEvent asLiveClassEvent() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof LiveClassEnqueuerEvent) {
+ LiveClassEnqueuerEvent event = (LiveClassEnqueuerEvent) obj;
+ return getType() == event.getType();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (getType().hashCode() << 1) | 0;
+ }
+ }
+
+ public static class InstantiatedClassEnqueuerEvent extends ClassEnqueuerEvent {
+
+ public InstantiatedClassEnqueuerEvent(DexProgramClass clazz) {
+ super(clazz);
+ }
+
+ @Override
+ public boolean isInstantiatedClassEvent() {
+ return true;
+ }
+
+ @Override
+ public InstantiatedClassEnqueuerEvent asInstantiatedClassEvent() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof InstantiatedClassEnqueuerEvent) {
+ InstantiatedClassEnqueuerEvent event = (InstantiatedClassEnqueuerEvent) obj;
+ return getType() == event.getType();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (getType().hashCode() << 1) | 1;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 38e40dd..4768437 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -5,6 +5,8 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -191,6 +193,24 @@
}
}
+ static class TraceAnnotationAction extends EnqueuerAction {
+ private final ProgramDefinition annotatedItem;
+ private final DexAnnotation annotation;
+ private final AnnotatedKind annotatedKind;
+
+ TraceAnnotationAction(
+ ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+ this.annotatedItem = annotatedItem;
+ this.annotation = annotation;
+ this.annotatedKind = annotatedKind;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.processAnnotation(annotatedItem, annotation, annotatedKind);
+ }
+ }
+
static class TraceCodeAction extends EnqueuerAction {
private final ProgramMethod method;
@@ -332,6 +352,9 @@
abstract void enqueueMarkFieldKeptAction(ProgramField field, KeepReasonWitness witness);
+ abstract void enqueueTraceAnnotationAction(
+ ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind);
+
public abstract void enqueueTraceCodeAction(ProgramMethod method);
public abstract void enqueueTraceConstClassAction(DexType type, ProgramMethod context);
@@ -431,6 +454,12 @@
}
@Override
+ void enqueueTraceAnnotationAction(
+ ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+ queue.add(new TraceAnnotationAction(annotatedItem, annotation, annotatedKind));
+ }
+
+ @Override
public void enqueueTraceCodeAction(ProgramMethod method) {
queue.add(new TraceCodeAction(method));
}
@@ -543,6 +572,12 @@
}
@Override
+ void enqueueTraceAnnotationAction(
+ ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+ throw attemptToEnqueue();
+ }
+
+ @Override
public void enqueueTraceCodeAction(ProgramMethod method) {
throw attemptToEnqueue();
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index e509230..1eeaa21 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -6,10 +6,14 @@
/** Globally controlled settings that affect the default values for kept items. */
public interface GlobalKeepInfoConfiguration {
+ boolean isAnnotationRemovalEnabled();
+
boolean isTreeShakingEnabled();
boolean isMinificationEnabled();
+ boolean isOptimizationEnabled();
+
boolean isAccessModificationEnabled();
boolean isRepackagingEnabled();
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index aa6844e..d5c8c68 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -8,6 +8,8 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -258,10 +260,14 @@
// combination as the size of member rules to satisfy.
for (Set<DexDefinition> combination :
Sets.combinations(filteredMembers, memberKeepRules.size())) {
- Collection<DexEncodedField> fieldsInCombination =
- DexDefinition.filterDexEncodedField(combination.stream()).collect(Collectors.toList());
- Collection<DexEncodedMethod> methodsInCombination =
- DexDefinition.filterDexEncodedMethod(combination.stream()).collect(Collectors.toList());
+ Collection<DexClassAndField> fieldsInCombination =
+ DexDefinition.filterDexEncodedField(
+ combination.stream(), field -> DexClassAndField.create(targetClass, field))
+ .collect(Collectors.toList());
+ Collection<DexClassAndMethod> methodsInCombination =
+ DexDefinition.filterDexEncodedMethod(
+ combination.stream(), method -> DexClassAndMethod.create(targetClass, method))
+ .collect(Collectors.toList());
// Member rules are combined as AND logic: if found unsatisfied member rule, this
// combination of live members is not a good fit.
boolean satisfied =
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index a12ca25..dc4a931 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -27,6 +27,10 @@
return BOTTOM;
}
+ public static Joiner newEmptyJoiner() {
+ return bottom().joiner();
+ }
+
private KeepClassInfo(Builder builder) {
super(builder);
}
@@ -52,7 +56,8 @@
GlobalKeepInfoConfiguration configuration, boolean kotlinMetadataKept) {
return !kotlinMetadataKept
|| !isPinned()
- || !configuration.isKeepRuntimeVisibleAnnotationsEnabled();
+ || !configuration.isKeepRuntimeVisibleAnnotationsEnabled()
+ || isAnnotationRemovalAllowed(configuration);
}
public static boolean isKotlinMetadataClassKept(AppView<?> appView) {
@@ -110,7 +115,7 @@
@Override
public boolean isEqualTo(KeepClassInfo other) {
- return true;
+ return internalIsEqualTo(other);
}
@Override
@@ -126,6 +131,17 @@
}
@Override
+ public Joiner asClassJoiner() {
+ return this;
+ }
+
+ @Override
+ public Joiner merge(Joiner joiner) {
+ // Should be extended to merge the fields of this class in case any are added.
+ return super.merge(joiner);
+ }
+
+ @Override
Joiner self() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index bdfb7cc..0c54589 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -20,6 +20,10 @@
return BOTTOM;
}
+ public static Joiner newEmptyJoiner() {
+ return bottom().joiner();
+ }
+
private KeepFieldInfo(Builder builder) {
super(builder);
}
@@ -73,7 +77,7 @@
@Override
public boolean isEqualTo(KeepFieldInfo other) {
- return true;
+ return internalIsEqualTo(other);
}
@Override
@@ -89,6 +93,17 @@
}
@Override
+ public Joiner asFieldJoiner() {
+ return this;
+ }
+
+ @Override
+ public Joiner merge(Joiner joiner) {
+ // Should be extended to merge the fields of this class in case any are added.
+ return super.merge(joiner);
+ }
+
+ @Override
Joiner self() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index acf36aa..b795aab 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -6,39 +6,71 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.shaking.KeepInfo.Builder;
+import java.util.function.Consumer;
/** Keep information that can be associated with any item, i.e., class, method or field. */
public abstract class KeepInfo<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
private final boolean pinned;
- private final boolean allowMinification;
private final boolean allowAccessModification;
+ private final boolean allowAnnotationRemoval;
+ private final boolean allowMinification;
+ private final boolean allowOptimization;
+ private final boolean allowShrinking;
private final boolean requireAccessModificationForRepackaging;
private KeepInfo(
boolean pinned,
- boolean allowMinification,
boolean allowAccessModification,
+ boolean allowAnnotationRemoval,
+ boolean allowMinification,
+ boolean allowOptimization,
+ boolean allowShrinking,
boolean requireAccessModificationForRepackaging) {
this.pinned = pinned;
- this.allowMinification = allowMinification;
this.allowAccessModification = allowAccessModification;
+ this.allowAnnotationRemoval = allowAnnotationRemoval;
+ this.allowMinification = allowMinification;
+ this.allowOptimization = allowOptimization;
+ this.allowShrinking = allowShrinking;
this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
}
KeepInfo(B builder) {
this(
builder.isPinned(),
- builder.isMinificationAllowed(),
builder.isAccessModificationAllowed(),
+ builder.isAnnotationRemovalAllowed(),
+ builder.isMinificationAllowed(),
+ builder.isOptimizationAllowed(),
+ builder.isShrinkingAllowed(),
builder.isAccessModificationRequiredForRepackaging());
}
abstract B builder();
- /** True if an item must be present in the output. */
+ /**
+ * True if an item may have all of its annotations removed.
+ *
+ * <p>If this returns false, some annotations may still be removed if the configuration does not
+ * keep all annotation attributes.
+ */
+ public boolean isAnnotationRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
+ return configuration.isAnnotationRemovalEnabled() && internalIsAnnotationRemovalAllowed();
+ }
+
+ boolean internalIsAnnotationRemovalAllowed() {
+ return allowAnnotationRemoval;
+ }
+
+ /**
+ * True if an item must be present in the output.
+ *
+ * @deprecated Prefer task dependent predicates.
+ */
+ @Deprecated
public boolean isPinned() {
- return pinned;
+ return pinned || !allowOptimization;
}
/**
@@ -56,6 +88,34 @@
}
/**
+ * True if an item may be optimized (i.e., the item is not soft pinned).
+ *
+ * <p>This method requires knowledge of the global configuration as that can override the concrete
+ * value on a given item.
+ */
+ public boolean isOptimizationAllowed(GlobalKeepInfoConfiguration configuration) {
+ return configuration.isOptimizationEnabled() && internalIsOptimizationAllowed();
+ }
+
+ boolean internalIsOptimizationAllowed() {
+ return allowOptimization;
+ }
+
+ /**
+ * True if an item is subject to shrinking (i.e., tree shaking).
+ *
+ * <p>This method requires knowledge of the global configuration as that can override the concrete
+ * value on a given item.
+ */
+ public boolean isShrinkingAllowed(GlobalKeepInfoConfiguration configuration) {
+ return configuration.isTreeShakingEnabled() && internalIsShrinkingAllowed();
+ }
+
+ boolean internalIsShrinkingAllowed() {
+ return allowShrinking;
+ }
+
+ /**
* True if an item may be repackaged.
*
* <p>This method requires knowledge of the global configuration as that can override the concrete
@@ -133,7 +193,11 @@
// An item is less, aka, lower in the lattice, if each of its attributes is at least as
// permissive of that on other.
return (!pinned || other.isPinned())
- && (allowAccessModification || !other.internalIsAccessModificationAllowed());
+ && (allowAccessModification || !other.internalIsAccessModificationAllowed())
+ && (allowAnnotationRemoval || !other.internalIsAnnotationRemovalAllowed())
+ && (allowMinification || !other.internalIsMinificationAllowed())
+ && (allowOptimization || !other.internalIsOptimizationAllowed())
+ && (allowShrinking || !other.internalIsShrinkingAllowed());
}
/** Builder to construct an arbitrary keep info object. */
@@ -151,8 +215,11 @@
private K original;
private boolean pinned;
- private boolean allowMinification;
private boolean allowAccessModification;
+ private boolean allowAnnotationRemoval;
+ private boolean allowMinification;
+ private boolean allowOptimization;
+ private boolean allowShrinking;
private boolean requireAccessModificationForRepackaging;
Builder() {
@@ -162,25 +229,34 @@
Builder(K original) {
this.original = original;
pinned = original.isPinned();
- allowMinification = original.internalIsMinificationAllowed();
allowAccessModification = original.internalIsAccessModificationAllowed();
+ allowAnnotationRemoval = original.internalIsAnnotationRemovalAllowed();
+ allowMinification = original.internalIsMinificationAllowed();
+ allowOptimization = original.internalIsOptimizationAllowed();
+ allowShrinking = original.internalIsShrinkingAllowed();
requireAccessModificationForRepackaging =
original.internalIsAccessModificationRequiredForRepackaging();
}
B makeTop() {
pin();
- disallowMinification();
- requireAccessModificationForRepackaging();
disallowAccessModification();
+ disallowAnnotationRemoval();
+ disallowMinification();
+ disallowOptimization();
+ disallowShrinking();
+ requireAccessModificationForRepackaging();
return self();
}
B makeBottom() {
unpin();
- allowMinification();
- unsetRequireAccessModificationForRepackaging();
allowAccessModification();
+ allowAnnotationRemoval();
+ allowMinification();
+ allowOptimization();
+ allowShrinking();
+ unsetRequireAccessModificationForRepackaging();
return self();
}
@@ -199,23 +275,21 @@
return doBuild();
}
- private boolean internalIsEqualTo(K other) {
+ boolean internalIsEqualTo(K other) {
return isPinned() == other.isPinned()
- && isMinificationAllowed() == other.internalIsMinificationAllowed()
- && isAccessModificationRequiredForRepackaging()
- == other.internalIsAccessModificationRequiredForRepackaging()
&& isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
- && isEqualTo(other);
+ && isAnnotationRemovalAllowed() == other.internalIsAnnotationRemovalAllowed()
+ && isMinificationAllowed() == other.internalIsMinificationAllowed()
+ && isOptimizationAllowed() == other.internalIsOptimizationAllowed()
+ && isShrinkingAllowed() == other.internalIsShrinkingAllowed()
+ && isAccessModificationRequiredForRepackaging()
+ == other.internalIsAccessModificationRequiredForRepackaging();
}
public boolean isPinned() {
return pinned;
}
- public boolean isMinificationAllowed() {
- return allowMinification;
- }
-
public boolean isAccessModificationRequiredForRepackaging() {
return requireAccessModificationForRepackaging;
}
@@ -224,6 +298,22 @@
return allowAccessModification;
}
+ public boolean isAnnotationRemovalAllowed() {
+ return allowAnnotationRemoval;
+ }
+
+ public boolean isMinificationAllowed() {
+ return allowMinification;
+ }
+
+ public boolean isOptimizationAllowed() {
+ return allowOptimization;
+ }
+
+ public boolean isShrinkingAllowed() {
+ return allowShrinking;
+ }
+
public B setPinned(boolean pinned) {
this.pinned = pinned;
return self();
@@ -250,6 +340,32 @@
return setAllowMinification(false);
}
+ public B setAllowOptimization(boolean allowOptimization) {
+ this.allowOptimization = allowOptimization;
+ return self();
+ }
+
+ public B allowOptimization() {
+ return setAllowOptimization(true);
+ }
+
+ public B disallowOptimization() {
+ return setAllowOptimization(false);
+ }
+
+ public B setAllowShrinking(boolean allowShrinking) {
+ this.allowShrinking = allowShrinking;
+ return self();
+ }
+
+ public B allowShrinking() {
+ return setAllowShrinking(true);
+ }
+
+ public B disallowShrinking() {
+ return setAllowShrinking(false);
+ }
+
public B setRequireAccessModificationForRepackaging(
boolean requireAccessModificationForRepackaging) {
this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
@@ -276,6 +392,19 @@
public B disallowAccessModification() {
return setAllowAccessModification(false);
}
+
+ public B setAllowAnnotationRemoval(boolean allowAnnotationRemoval) {
+ this.allowAnnotationRemoval = allowAnnotationRemoval;
+ return self();
+ }
+
+ public B allowAnnotationRemoval() {
+ return setAllowAnnotationRemoval(true);
+ }
+
+ public B disallowAnnotationRemoval() {
+ return setAllowAnnotationRemoval(false);
+ }
}
/** Joiner to construct monotonically increasing keep info object. */
@@ -284,12 +413,35 @@
abstract J self();
- private final Builder<B, K> builder;
+ final Builder<B, K> builder;
Joiner(Builder<B, K> builder) {
this.builder = builder;
}
+ public J applyIf(boolean condition, Consumer<J> thenConsumer) {
+ if (condition) {
+ thenConsumer.accept(self());
+ }
+ return self();
+ }
+
+ public KeepClassInfo.Joiner asClassJoiner() {
+ return null;
+ }
+
+ public KeepFieldInfo.Joiner asFieldJoiner() {
+ return null;
+ }
+
+ public KeepMethodInfo.Joiner asMethodJoiner() {
+ return null;
+ }
+
+ public boolean isBottom() {
+ return builder.isEqualTo(builder.getBottomInfo());
+ }
+
public boolean isTop() {
return builder.isEqualTo(builder.getTopInfo());
}
@@ -304,21 +456,50 @@
return self();
}
- public J disallowMinification() {
- builder.disallowMinification();
- return self();
- }
-
public J disallowAccessModification() {
builder.disallowAccessModification();
return self();
}
+ public J disallowAnnotationRemoval() {
+ builder.disallowAnnotationRemoval();
+ return self();
+ }
+
+ public J disallowMinification() {
+ builder.disallowMinification();
+ return self();
+ }
+
+ public J disallowOptimization() {
+ builder.disallowOptimization();
+ return self();
+ }
+
+ public J disallowShrinking() {
+ builder.disallowShrinking();
+ return self();
+ }
+
public J requireAccessModificationForRepackaging() {
builder.requireAccessModificationForRepackaging();
return self();
}
+ public J merge(J joiner) {
+ Builder<B, K> builder = joiner.builder;
+ applyIf(builder.isPinned(), Joiner::pin);
+ applyIf(!builder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
+ applyIf(!builder.isAnnotationRemovalAllowed(), Joiner::disallowAnnotationRemoval);
+ applyIf(!builder.isMinificationAllowed(), Joiner::disallowMinification);
+ applyIf(!builder.isOptimizationAllowed(), Joiner::disallowOptimization);
+ applyIf(!builder.isShrinkingAllowed(), Joiner::disallowShrinking);
+ applyIf(
+ builder.isAccessModificationRequiredForRepackaging(),
+ Joiner::requireAccessModificationForRepackaging);
+ return self();
+ }
+
public K join() {
K joined = builder.build();
K original = builder.original;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index ddd5bfc..71068d2 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.shaking;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
@@ -22,17 +25,25 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MapUtils;
import com.google.common.collect.Streams;
-import java.util.ArrayList;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
// Non-mutable collection of keep information pertaining to a program.
public abstract class KeepInfoCollection {
+ abstract void forEachRuleInstance(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ BiConsumer<DexProgramClass, KeepClassInfo.Joiner> classRuleInstanceConsumer,
+ BiConsumer<ProgramField, KeepFieldInfo.Joiner> fieldRuleInstanceConsumer,
+ BiConsumer<ProgramMethod, KeepMethodInfo.Joiner> methodRuleInstanceConsumer);
+
// TODO(b/157538235): This should not be bottom.
private static KeepClassInfo keepInfoForNonProgramClass() {
return KeepClassInfo.bottom();
@@ -48,8 +59,6 @@
return KeepFieldInfo.bottom();
}
- abstract Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances();
-
/**
* Base accessor for keep info on a class.
*
@@ -195,13 +204,17 @@
private final Map<DexField, KeepFieldInfo> keepFieldInfo;
// Map of applied rules for which keys may need to be mutated.
- private final Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances;
+ private final Map<DexType, KeepClassInfo.Joiner> classRuleInstances;
+ private final Map<DexField, KeepFieldInfo.Joiner> fieldRuleInstances;
+ private final Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances;
MutableKeepInfoCollection() {
this(
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
+ new IdentityHashMap<>(),
+ new IdentityHashMap<>(),
new IdentityHashMap<>());
}
@@ -209,11 +222,15 @@
Map<DexType, KeepClassInfo> keepClassInfo,
Map<DexMethod, KeepMethodInfo> keepMethodInfo,
Map<DexField, KeepFieldInfo> keepFieldInfo,
- Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances) {
+ Map<DexType, KeepClassInfo.Joiner> classRuleInstances,
+ Map<DexField, KeepFieldInfo.Joiner> fieldRuleInstances,
+ Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances) {
this.keepClassInfo = keepClassInfo;
this.keepMethodInfo = keepMethodInfo;
this.keepFieldInfo = keepFieldInfo;
- this.ruleInstances = ruleInstances;
+ this.classRuleInstances = classRuleInstances;
+ this.fieldRuleInstances = fieldRuleInstances;
+ this.methodRuleInstances = methodRuleInstances;
}
public void removeKeepInfoForPrunedItems(Set<DexType> removedClasses) {
@@ -265,42 +282,96 @@
KeepFieldInfo previous = newFieldInfo.put(newField, info);
assert previous == null;
});
- Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> newRuleInstances =
- new IdentityHashMap<>(ruleInstances.size());
- ruleInstances.forEach(
- (reference, consumers) -> {
- DexReference newReference;
- if (reference.isDexType()) {
- DexType newType = lens.lookupType(reference.asDexType());
- if (!newType.isClassType()) {
- assert newType.isIntType() : "Expected only enum unboxing type changes.";
- return;
- }
- newReference = newType;
- } else if (reference.isDexMethod()) {
- newReference = lens.getRenamedMethodSignature(reference.asDexMethod());
- } else {
- assert reference.isDexField();
- newReference = lens.getRenamedFieldSignature(reference.asDexField());
- }
- newRuleInstances.put(newReference, consumers);
- });
return new MutableKeepInfoCollection(
- newClassInfo, newMethodInfo, newFieldInfo, newRuleInstances);
+ newClassInfo,
+ newMethodInfo,
+ newFieldInfo,
+ rewriteRuleInstances(
+ classRuleInstances,
+ clazz -> {
+ DexType rewritten = lens.lookupType(clazz);
+ if (rewritten.isClassType()) {
+ return rewritten;
+ }
+ assert rewritten.isIntType();
+ return null;
+ },
+ KeepClassInfo::newEmptyJoiner),
+ rewriteRuleInstances(
+ fieldRuleInstances, lens::getRenamedFieldSignature, KeepFieldInfo::newEmptyJoiner),
+ rewriteRuleInstances(
+ methodRuleInstances,
+ lens::getRenamedMethodSignature,
+ KeepMethodInfo::newEmptyJoiner));
+ }
+
+ private static <R, J extends KeepInfo.Joiner<J, ?, ?>> Map<R, J> rewriteRuleInstances(
+ Map<R, J> ruleInstances, Function<R, R> rewriter, Supplier<J> newEmptyJoiner) {
+ return MapUtils.transform(
+ ruleInstances,
+ IdentityHashMap::new,
+ rewriter,
+ Function.identity(),
+ (joiner, otherJoiner) -> newEmptyJoiner.get().merge(joiner).merge(otherJoiner));
}
@Override
- Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances() {
- return ruleInstances;
+ void forEachRuleInstance(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ BiConsumer<DexProgramClass, KeepClassInfo.Joiner> classRuleInstanceConsumer,
+ BiConsumer<ProgramField, KeepFieldInfo.Joiner> fieldRuleInstanceConsumer,
+ BiConsumer<ProgramMethod, KeepMethodInfo.Joiner> methodRuleInstanceConsumer) {
+ classRuleInstances.forEach(
+ (type, ruleInstance) -> {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz != null) {
+ classRuleInstanceConsumer.accept(clazz, ruleInstance);
+ }
+ });
+ fieldRuleInstances.forEach(
+ (fieldReference, ruleInstance) -> {
+ DexProgramClass holder =
+ asProgramClassOrNull(appView.definitionFor(fieldReference.getHolderType()));
+ ProgramField field = holder.lookupProgramField(fieldReference);
+ if (field != null) {
+ fieldRuleInstanceConsumer.accept(field, ruleInstance);
+ }
+ });
+ methodRuleInstances.forEach(
+ (methodReference, ruleInstance) -> {
+ DexProgramClass holder =
+ asProgramClassOrNull(appView.definitionFor(methodReference.getHolderType()));
+ ProgramMethod method = holder.lookupProgramMethod(methodReference);
+ if (method != null) {
+ methodRuleInstanceConsumer.accept(method, ruleInstance);
+ }
+ });
}
- void evaluateRule(
- DexReference reference,
- DexDefinitionSupplier definitions,
- Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
- joinInfo(reference, definitions, fn);
- if (!getInfo(reference, definitions).isBottom()) {
- ruleInstances.computeIfAbsent(reference, k -> new ArrayList<>()).add(fn);
+ void evaluateClassRule(DexProgramClass clazz, KeepClassInfo.Joiner minimumKeepInfo) {
+ if (!minimumKeepInfo.isBottom()) {
+ joinClass(clazz, joiner -> joiner.merge(minimumKeepInfo));
+ classRuleInstances
+ .computeIfAbsent(clazz.getType(), ignoreKey(KeepClassInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ void evaluateFieldRule(ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
+ if (!minimumKeepInfo.isBottom()) {
+ joinField(field, joiner -> joiner.merge(minimumKeepInfo));
+ fieldRuleInstances
+ .computeIfAbsent(field.getReference(), ignoreKey(KeepFieldInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
+ }
+ }
+
+ void evaluateMethodRule(ProgramMethod method, KeepMethodInfo.Joiner minimumKeepInfo) {
+ if (!minimumKeepInfo.isBottom()) {
+ joinMethod(method, joiner -> joiner.merge(minimumKeepInfo));
+ methodRuleInstances
+ .computeIfAbsent(method.getReference(), ignoreKey(KeepMethodInfo::newEmptyJoiner))
+ .merge(minimumKeepInfo);
}
}
@@ -321,9 +392,10 @@
return keepFieldInfo.getOrDefault(field.getReference(), KeepFieldInfo.bottom());
}
- public void joinClass(DexProgramClass clazz, Consumer<KeepClassInfo.Joiner> fn) {
+ public void joinClass(DexProgramClass clazz, Consumer<? super KeepClassInfo.Joiner> fn) {
KeepClassInfo info = getClassInfo(clazz);
if (info.isTop()) {
+ assert info == KeepClassInfo.top();
return;
}
KeepClassInfo.Joiner joiner = info.joiner();
@@ -334,34 +406,6 @@
}
}
- public void joinInfo(
- DexReference reference,
- DexDefinitionSupplier definitions,
- Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
- if (reference.isDexType()) {
- DexType type = reference.asDexType();
- DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
- if (clazz != null) {
- joinClass(clazz, fn::accept);
- }
- } else if (reference.isDexMethod()) {
- DexMethod method = reference.asDexMethod();
- DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(method.holder));
- ProgramMethod definition = method.lookupOnProgramClass(clazz);
- if (definition != null) {
- joinMethod(definition, fn::accept);
- }
- } else {
- assert reference.isDexField();
- DexField field = reference.asDexField();
- DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(field.holder));
- ProgramField definition = field.lookupOnProgramClass(clazz);
- if (definition != null) {
- joinField(definition, fn::accept);
- }
- }
- }
-
public void keepClass(DexProgramClass clazz) {
joinClass(clazz, KeepInfo.Joiner::top);
}
@@ -370,9 +414,10 @@
joinClass(clazz, KeepInfo.Joiner::pin);
}
- public void joinMethod(ProgramMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
+ public void joinMethod(ProgramMethod method, Consumer<? super KeepMethodInfo.Joiner> fn) {
KeepMethodInfo info = getMethodInfo(method);
- if (info == KeepMethodInfo.top()) {
+ if (info.isTop()) {
+ assert info == KeepMethodInfo.top();
return;
}
KeepMethodInfo.Joiner joiner = info.joiner();
@@ -423,9 +468,10 @@
}
}
- public void joinField(ProgramField field, Consumer<KeepFieldInfo.Joiner> fn) {
+ public void joinField(ProgramField field, Consumer<? super KeepFieldInfo.Joiner> fn) {
KeepFieldInfo info = getFieldInfo(field);
if (info.isTop()) {
+ assert info == KeepFieldInfo.top();
return;
}
Joiner joiner = info.joiner();
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index ab243b6..1669a40 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -20,6 +20,10 @@
return BOTTOM;
}
+ public static Joiner newEmptyJoiner() {
+ return bottom().joiner();
+ }
+
private KeepMethodInfo(Builder builder) {
super(builder);
}
@@ -73,7 +77,7 @@
@Override
public boolean isEqualTo(KeepMethodInfo other) {
- return true;
+ return internalIsEqualTo(other);
}
@Override
@@ -89,6 +93,17 @@
}
@Override
+ public Joiner asMethodJoiner() {
+ return this;
+ }
+
+ @Override
+ public Joiner merge(Joiner joiner) {
+ // Should be extended to merge the fields of this class in case any are added.
+ return super.merge(joiner);
+ }
+
+ @Override
Joiner self() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 49c4a1e..948caf2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -388,6 +388,12 @@
// TODO(b/189807246): This should be removed.
modifiers.setAllowsOptimization(true);
modifiers.setAllowsObfuscation(isObfuscating());
+
+ // In non-compatibility mode, adding -dontoptimize does not cause all annotations
+ // to be retained.
+ if (!forceProguardCompatibility && isShrinking()) {
+ modifiers.setAllowsAnnotationRemoval(true);
+ }
});
addRule(rule);
this.keepAllRule = rule;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index ff71812..3072667 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1024,6 +1024,10 @@
builder.getModifiersBuilder().setAllowsObfuscation(true);
} else if (acceptString("accessmodification")) {
builder.getModifiersBuilder().setAllowsAccessModification(true);
+ } else if (allowTestOptions) {
+ if (acceptString("annotationremoval")) {
+ builder.getModifiersBuilder().setAllowsAnnotationRemoval(true);
+ }
}
} else if (acceptString("includedescriptorclasses")) {
builder.getModifiersBuilder().setIncludeDescriptorClasses(true);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index a02117d..3021c88 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -7,6 +7,7 @@
public static class Builder {
private boolean allowsAccessModification = false;
+ private boolean allowsAnnotationRemoval = false;
private boolean allowsShrinking = false;
private boolean allowsOptimization = false;
private boolean allowsObfuscation = false;
@@ -19,6 +20,11 @@
return this;
}
+ public Builder setAllowsAnnotationRemoval(boolean allowsAnnotationRemoval) {
+ this.allowsAnnotationRemoval = allowsAnnotationRemoval;
+ return this;
+ }
+
public void setAllowsShrinking(boolean allowsShrinking) {
this.allowsShrinking = allowsShrinking;
}
@@ -39,6 +45,7 @@
ProguardKeepRuleModifiers build() {
return new ProguardKeepRuleModifiers(
allowsAccessModification,
+ allowsAnnotationRemoval,
allowsShrinking,
allowsOptimization,
allowsObfuscation,
@@ -47,6 +54,7 @@
}
public final boolean allowsAccessModification;
+ public final boolean allowsAnnotationRemoval;
public final boolean allowsShrinking;
public final boolean allowsOptimization;
public final boolean allowsObfuscation;
@@ -54,11 +62,13 @@
private ProguardKeepRuleModifiers(
boolean allowsAccessModification,
+ boolean allowsAnnotationRemoval,
boolean allowsShrinking,
boolean allowsOptimization,
boolean allowsObfuscation,
boolean includeDescriptorClasses) {
this.allowsAccessModification = allowsAccessModification;
+ this.allowsAnnotationRemoval = allowsAnnotationRemoval;
this.allowsShrinking = allowsShrinking;
this.allowsOptimization = allowsOptimization;
this.allowsObfuscation = allowsObfuscation;
@@ -72,6 +82,15 @@
return new Builder();
}
+ public boolean isBottom() {
+ return allowsAccessModification
+ && allowsAnnotationRemoval
+ && allowsObfuscation
+ && allowsOptimization
+ && allowsShrinking
+ && !includeDescriptorClasses;
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof ProguardKeepRuleModifiers)) {
@@ -79,6 +98,7 @@
}
ProguardKeepRuleModifiers that = (ProguardKeepRuleModifiers) o;
return allowsAccessModification == that.allowsAccessModification
+ && allowsAnnotationRemoval == that.allowsAnnotationRemoval
&& allowsShrinking == that.allowsShrinking
&& allowsOptimization == that.allowsOptimization
&& allowsObfuscation == that.allowsObfuscation
@@ -88,16 +108,18 @@
@Override
public int hashCode() {
return (allowsAccessModification ? 1 : 0)
- | (allowsShrinking ? 2 : 0)
- | (allowsOptimization ? 4 : 0)
- | (allowsObfuscation ? 8 : 0)
- | (includeDescriptorClasses ? 16 : 0);
+ | (allowsAnnotationRemoval ? 2 : 0)
+ | (allowsShrinking ? 4 : 0)
+ | (allowsOptimization ? 8 : 0)
+ | (allowsObfuscation ? 16 : 0)
+ | (includeDescriptorClasses ? 32 : 0);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
appendWithComma(builder, allowsAccessModification, "allowaccessmodification");
+ appendWithComma(builder, allowsAnnotationRemoval, "allowannotationremoval");
appendWithComma(builder, allowsObfuscation, "allowobfuscation");
appendWithComma(builder, allowsShrinking, "allowshrinking");
appendWithComma(builder, allowsOptimization, "allowoptimization");
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 2aba15e..562f6b0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -181,7 +181,7 @@
}
public boolean matches(
- DexEncodedField field,
+ DexClassAndField field,
AppView<?> appView,
Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
DexStringCache stringCache) {
@@ -192,8 +192,8 @@
case ALL_FIELDS:
{
// Access flags check.
- if (!getAccessFlags().containsAll(field.accessFlags)
- || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
+ if (!getAccessFlags().containsAll(field.getAccessFlags())
+ || !getNegatedAccessFlags().containsNone(field.getAccessFlags())) {
break;
}
// Annotations check.
@@ -209,8 +209,8 @@
break;
}
// Access flags check.
- if (!getAccessFlags().containsAll(field.accessFlags)
- || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
+ if (!getAccessFlags().containsAll(field.getAccessFlags())
+ || !getNegatedAccessFlags().containsNone(field.getAccessFlags())) {
break;
}
// Type check.
@@ -233,7 +233,7 @@
}
public boolean matches(
- DexEncodedMethod method,
+ DexClassAndMethod method,
AppView<?> appView,
Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
DexStringCache stringCache) {
@@ -241,7 +241,7 @@
appView.graphLens().getOriginalMethodSignature(method.getReference());
switch (getRuleType()) {
case ALL_METHODS:
- if (method.isClassInitializer()) {
+ if (method.getDefinition().isClassInitializer()) {
break;
}
// Fall through for all other methods.
@@ -249,8 +249,8 @@
case ALL:
{
// Access flags check.
- if (!getAccessFlags().containsAll(method.accessFlags)
- || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
+ if (!getAccessFlags().containsAll(method.getAccessFlags())
+ || !getNegatedAccessFlags().containsNone(method.getAccessFlags())) {
break;
}
// Annotations check.
@@ -260,7 +260,7 @@
case METHOD:
// Check return type.
- if (!type.matches(originalSignature.proto.returnType, appView)) {
+ if (!type.matches(originalSignature.getReturnType(), appView)) {
break;
}
// Fall through for access flags, name and arguments.
@@ -275,8 +275,8 @@
break;
}
// Access flags check.
- if (!getAccessFlags().containsAll(method.accessFlags)
- || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
+ if (!getAccessFlags().containsAll(method.getAccessFlags())
+ || !getNegatedAccessFlags().containsNone(method.getAccessFlags())) {
break;
}
// Annotations check.
@@ -289,7 +289,7 @@
if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) {
return true;
}
- DexType[] parameters = originalSignature.proto.parameters.values;
+ DexType[] parameters = originalSignature.getParameters().values;
if (parameters.length != arguments.size()) {
break;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index e4890a0..28094a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -5,6 +5,8 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+import static java.util.Collections.emptyMap;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
@@ -12,9 +14,14 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.Definition;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
@@ -29,6 +36,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
@@ -39,7 +47,11 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
+import com.android.tools.r8.shaking.AnnotationMatchResult.MatchedAnnotation;
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.shaking.EnqueuerEvent.InstantiatedClassEnqueuerEvent;
+import com.android.tools.r8.shaking.EnqueuerEvent.LiveClassEnqueuerEvent;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.Consumer3;
import com.android.tools.r8.utils.InternalOptions;
@@ -48,6 +60,7 @@
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.TriConsumer;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -83,6 +96,39 @@
public class RootSetUtils {
+ static void modifyMinimumKeepInfo(
+ Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+ DexDefinition definition,
+ Consumer<Joiner<?, ?, ?>> consumer) {
+ modifyMinimumKeepInfo(minimumKeepInfo, definition.getReference(), consumer);
+ }
+
+ static void modifyMinimumKeepInfo(
+ Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+ DexReference reference,
+ Consumer<Joiner<?, ?, ?>> consumer) {
+ Joiner<?, ?, ?> joiner =
+ minimumKeepInfo.computeIfAbsent(
+ reference,
+ key ->
+ key.apply(
+ clazz -> KeepClassInfo.newEmptyJoiner(),
+ field -> KeepFieldInfo.newEmptyJoiner(),
+ method -> KeepMethodInfo.newEmptyJoiner()));
+ consumer.accept(joiner);
+ assert !joiner.isBottom();
+ }
+
+ static void modifyDependentMinimumKeepInfo(
+ Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
+ EnqueuerEvent event,
+ DexDefinition dependent,
+ Consumer<Joiner<?, ?, ?>> consumer) {
+ Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfoForDependants =
+ dependentMinimumKeepInfo.computeIfAbsent(event, ignoreKey(IdentityHashMap::new));
+ modifyMinimumKeepInfo(minimumKeepInfoForDependants, dependent, consumer);
+ }
+
public static class RootSetBuilder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
@@ -90,8 +136,10 @@
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
- private final MutableItemsWithRules softPinned = new MutableItemsWithRules();
- private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
+ private final Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo =
+ new IdentityHashMap<>();
+ private final Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>>
+ dependentMinimumKeepInfo = new HashMap<>();
private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
@@ -112,8 +160,6 @@
private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
new IdentityHashMap<>();
- private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned =
- new IdentityHashMap<>();
private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
@@ -184,7 +230,7 @@
}
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier;
if (rule instanceof ProguardKeepRule) {
if (clazz.isNotProgramClass()) {
return;
@@ -192,7 +238,7 @@
switch (((ProguardKeepRule) rule).getType()) {
case KEEP_CLASS_MEMBERS:
// Members mentioned at -keepclassmembers always depend on their holder.
- preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
+ preconditionSupplier = ImmutableMap.of(definition -> true, clazz.asProgramClass());
markMatchingVisibleMethods(
clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
markMatchingVisibleFields(
@@ -210,7 +256,8 @@
// Static members in -keep are pinned no matter what.
preconditionSupplier.put(DexDefinition::isStaticMember, null);
// Instance members may need to be kept even though the holder is not instantiated.
- preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
+ preconditionSupplier.put(
+ definition -> !definition.isStaticMember(), clazz.asProgramClass());
} else {
// Members mentioned at -keep should always be pinned as long as that -keep rule is
// not triggered conditionally.
@@ -231,10 +278,16 @@
if (rule instanceof ProguardIfRule) {
throw new Unreachable("-if rule will be evaluated separately, not here.");
} else if (rule instanceof ProguardCheckDiscardRule) {
- if (memberKeepRules.isEmpty()) {
+ if (!clazz.isProgramClass()) {
+ appView
+ .reporter()
+ .warning(
+ new StringDiagnostic(
+ "The rule `" + rule + "` matches a class not in the program."));
+ } else if (memberKeepRules.isEmpty()) {
markClass(clazz, rule, ifRule);
} else {
- preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+ preconditionSupplier = ImmutableMap.of((definition -> true), clazz.asProgramClass());
markMatchingVisibleMethods(
clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
markMatchingVisibleFields(
@@ -346,11 +399,10 @@
assert Sets.intersection(neverInline, alwaysInline).isEmpty()
&& Sets.intersection(neverInline, forceInline).isEmpty()
: "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
- assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
return new RootSet(
noShrinking,
- softPinned,
- noObfuscation,
+ minimumKeepInfo,
+ dependentMinimumKeepInfo,
ImmutableList.copyOf(reasonAsked.values()),
ImmutableList.copyOf(checkDiscarded.values()),
alwaysInline,
@@ -373,7 +425,6 @@
noSideEffects,
assumedValues,
dependentNoShrinking,
- dependentSoftPinned,
dependentKeepClassCompatRule,
identifierNameStrings,
ifRules,
@@ -446,23 +497,23 @@
neverInlineDueToSingleCaller,
neverClassInline,
noShrinking,
- softPinned,
- noObfuscation,
+ minimumKeepInfo,
+ dependentMinimumKeepInfo,
dependentNoShrinking,
- dependentSoftPinned,
dependentKeepClassCompatRule,
Lists.newArrayList(delayedRootSetActionItems));
}
- private static DexDefinition testAndGetPrecondition(
+ private static DexProgramClass testAndGetPrecondition(
DexDefinition definition,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier) {
if (preconditionSupplier == null) {
return null;
}
- DexDefinition precondition = null;
+ DexProgramClass precondition = null;
boolean conditionEverMatched = false;
- for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
+ for (Entry<Predicate<DexDefinition>, DexProgramClass> entry :
+ preconditionSupplier.entrySet()) {
if (entry.getKey().test(definition)) {
precondition = entry.getValue();
conditionEverMatched = true;
@@ -479,7 +530,7 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
boolean includeLibraryClasses,
ProguardIfRule ifRule) {
Set<Wrapper<DexMethod>> methodsMarked =
@@ -492,24 +543,17 @@
break;
}
// In compat mode traverse all direct methods in the hierarchy.
- currentClass
- .getMethodCollection()
- .forEachDirectMethodMatching(
- method ->
- currentClass == clazz
- || (method.isStatic() && !method.isPrivate() && !method.isInitializer())
- || options.forceProguardCompatibility,
- method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
- });
- currentClass
- .virtualMethods()
- .forEach(
- method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
- });
+ currentClass.forEachClassMethodMatching(
+ method ->
+ method.belongsToVirtualPool()
+ || currentClass == clazz
+ || (method.isStatic() && !method.isPrivate() && !method.isInitializer())
+ || options.forceProguardCompatibility,
+ method -> {
+ DexProgramClass precondition =
+ testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
+ markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
+ });
if (currentClass.superType != null) {
DexClass dexClass = application.definitionFor(currentClass.superType);
if (dexClass != null) {
@@ -539,7 +583,7 @@
private final DexProgramClass originalClazz;
private final Collection<ProguardMemberRule> memberKeepRules;
private final ProguardConfigurationRule context;
- private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+ private final Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier;
private final ProguardIfRule ifRule;
private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
@@ -548,7 +592,7 @@
DexProgramClass originalClazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule context,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
ProguardIfRule ifRule) {
assert context.isProguardKeepRule();
assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
@@ -582,21 +626,24 @@
if (originalClazz == clazz) {
return;
}
- for (DexEncodedMethod method : clazz.virtualMethods()) {
- // Check if we already added this.
- Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.getReference());
- if (!seenMethods.add(wrapped)) {
- continue;
- }
- for (ProguardMemberRule rule : memberKeepRules) {
- if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
- tryAndKeepMethodOnClass(method, rule);
- }
- }
- }
+ clazz.forEachClassMethodMatching(
+ DexEncodedMethod::belongsToVirtualPool,
+ method -> {
+ // Check if we already added this.
+ Wrapper<DexMethod> wrapped =
+ MethodSignatureEquivalence.get().wrap(method.getReference());
+ if (!seenMethods.add(wrapped)) {
+ return;
+ }
+ for (ProguardMemberRule rule : memberKeepRules) {
+ if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+ tryAndKeepMethodOnClass(method, rule);
+ }
+ }
+ });
}
- private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
+ private void tryAndKeepMethodOnClass(DexClassAndMethod method, ProguardMemberRule rule) {
SingleResolutionResult resolutionResult =
appView
.appInfo()
@@ -637,7 +684,7 @@
context,
rule);
}
- DexDefinition precondition =
+ DexProgramClass precondition =
testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
rootSetBuilder.addItemToSets(
methodToKeep.getDefinition(), context, rule, precondition, ifRule);
@@ -655,7 +702,7 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
boolean onlyIncludeProgramClasses,
ProguardIfRule ifRule) {
Set<DexType> visited = new HashSet<>();
@@ -676,13 +723,13 @@
if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) {
continue;
}
- currentClazz
- .virtualMethods()
- .forEach(
- method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
- });
+ currentClazz.forEachClassMethodMatching(
+ DexEncodedMethod::belongsToVirtualPool,
+ method -> {
+ DexProgramClass precondition =
+ testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
+ markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
+ });
worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
}
}
@@ -691,11 +738,12 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
ProguardIfRule ifRule) {
- clazz.forEachMethod(
+ clazz.forEachClassMethod(
method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+ DexProgramClass precondition =
+ testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
});
}
@@ -704,16 +752,17 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
boolean includeLibraryClasses,
ProguardIfRule ifRule) {
while (clazz != null) {
if (!includeLibraryClasses && clazz.isNotProgramClass()) {
return;
}
- clazz.forEachField(
+ clazz.forEachClassField(
field -> {
- DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+ DexProgramClass precondition =
+ testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRule);
});
clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
@@ -724,11 +773,12 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
ProguardIfRule ifRule) {
- clazz.forEachField(
+ clazz.forEachClassField(
field -> {
- DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+ DexProgramClass precondition =
+ testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRule);
});
}
@@ -924,15 +974,13 @@
* account.
*/
private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
- return ruleSatisfiedByMethods(rule, clazz.directMethods())
- || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
- || ruleSatisfiedByFields(rule, clazz.staticFields())
- || ruleSatisfiedByFields(rule, clazz.instanceFields());
+ return ruleSatisfiedByMethods(rule, clazz.classMethods())
+ || ruleSatisfiedByFields(rule, clazz.classFields());
}
- boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
+ boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexClassAndMethod> methods) {
if (rule.getRuleType().includesMethods()) {
- for (DexEncodedMethod method : methods) {
+ for (DexClassAndMethod method : methods) {
if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
return true;
}
@@ -941,9 +989,9 @@
return false;
}
- boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
+ boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexClassAndField> fields) {
if (rule.getRuleType().includesFields()) {
- for (DexEncodedField field : fields) {
+ for (DexClassAndField field : fields) {
if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
return true;
}
@@ -954,25 +1002,34 @@
static AnnotationMatchResult containsAllAnnotations(
List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
- return containsAllAnnotations(annotationMatchers, clazz.annotations());
+ return containsAllAnnotations(
+ annotationMatchers, clazz, clazz.annotations(), AnnotatedKind.TYPE);
}
static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
boolean containsAllAnnotations(
List<ProguardTypeMatcher> annotationMatchers,
- DexEncodedMember<D, R> member,
+ DexClassAndMember<D, R> member,
Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
AnnotationMatchResult annotationMatchResult =
- containsAllAnnotations(annotationMatchers, member.annotations());
+ containsAllAnnotations(
+ annotationMatchers,
+ member,
+ member.getAnnotations(),
+ member.isField() ? AnnotatedKind.FIELD : AnnotatedKind.METHOD);
if (annotationMatchResult != null) {
matchedAnnotationsConsumer.accept(annotationMatchResult);
return true;
}
- if (member.isDexEncodedMethod()) {
- DexEncodedMethod method = member.asDexEncodedMethod();
- for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
+ if (member.isMethod()) {
+ DexClassAndMethod method = member.asMethod();
+ for (int i = 0; i < method.getParameterAnnotations().size(); i++) {
annotationMatchResult =
- containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
+ containsAllAnnotations(
+ annotationMatchers,
+ method,
+ method.getParameterAnnotation(i),
+ AnnotatedKind.PARAMETER);
if (annotationMatchResult != null) {
matchedAnnotationsConsumer.accept(annotationMatchResult);
return true;
@@ -983,18 +1040,25 @@
}
private static AnnotationMatchResult containsAllAnnotations(
- List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
+ List<ProguardTypeMatcher> annotationMatchers,
+ Definition annotatedItem,
+ DexAnnotationSet annotations,
+ AnnotatedKind annotatedKind) {
if (annotationMatchers.isEmpty()) {
return AnnotationsIgnoredMatchResult.getInstance();
}
- List<DexAnnotation> matchedAnnotations = new ArrayList<>();
+ List<MatchedAnnotation> matchedAnnotations = new ArrayList<>();
for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
DexAnnotation matchedAnnotation =
getFirstAnnotationThatMatches(annotationMatcher, annotations);
if (matchedAnnotation == null) {
return null;
}
- matchedAnnotations.add(matchedAnnotation);
+ if (annotatedItem.isProgramDefinition()) {
+ matchedAnnotations.add(
+ new MatchedAnnotation(
+ annotatedItem.asProgramDefinition(), matchedAnnotation, annotatedKind));
+ }
}
return new ConcreteAnnotationMatchResult(matchedAnnotations);
}
@@ -1010,11 +1074,11 @@
}
private void markMethod(
- DexEncodedMethod method,
+ DexClassAndMethod method,
Collection<ProguardMemberRule> rules,
Set<Wrapper<DexMethod>> methodsMarked,
ProguardConfigurationRule context,
- DexDefinition precondition,
+ DexProgramClass precondition,
ProguardIfRule ifRule) {
if (methodsMarked != null
&& methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.getReference()))) {
@@ -1030,23 +1094,23 @@
if (methodsMarked != null) {
methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.getReference()));
}
- addItemToSets(method, context, rule, precondition, ifRule);
+ addItemToSets(method.getDefinition(), context, rule, precondition, ifRule);
}
}
}
private void markField(
- DexEncodedField field,
+ DexClassAndField field,
Collection<ProguardMemberRule> rules,
ProguardConfigurationRule context,
- DexDefinition precondition,
+ DexProgramClass precondition,
ProguardIfRule ifRule) {
for (ProguardMemberRule rule : rules) {
if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context, rule);
}
- addItemToSets(field, context, rule, precondition, ifRule);
+ addItemToSets(field.getDefinition(), context, rule, precondition, ifRule);
}
}
}
@@ -1058,7 +1122,12 @@
addItemToSets(clazz, rule, null, null, ifRule);
}
- private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) {
+ // TODO(b/192636793): This needs to use the precondition.
+ private void includeDescriptor(
+ DexDefinition item,
+ DexType type,
+ ProguardKeepRuleBase context,
+ DexProgramClass precondition) {
if (type.isVoidType()) {
return;
}
@@ -1068,30 +1137,33 @@
if (type.isPrimitiveType()) {
return;
}
- DexClass definition = appView.definitionFor(type);
- if (definition == null || definition.isNotProgramClass()) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz == null) {
return;
}
+
// Keep the type if the item is also kept.
dependentNoShrinking
.computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
.addClassWithRule(type, context);
- // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
+
+ // Disable minification for the type.
if (appView.options().isMinificationEnabled()) {
- noObfuscation.add(type);
+ modifyMinimumKeepInfo(minimumKeepInfo, clazz, Joiner::disallowMinification);
}
}
- private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
+ private void includeDescriptorClasses(
+ DexDefinition item, ProguardKeepRuleBase context, DexProgramClass precondition) {
if (item.isDexEncodedMethod()) {
- DexMethod method = item.asDexEncodedMethod().getReference();
- includeDescriptor(item, method.proto.returnType, context);
- for (DexType value : method.proto.parameters.values) {
- includeDescriptor(item, value, context);
+ DexEncodedMethod method = item.asDexEncodedMethod();
+ includeDescriptor(item, method.getReturnType(), context, precondition);
+ for (DexType value : method.getParameters()) {
+ includeDescriptor(item, value, context, precondition);
}
} else if (item.isDexEncodedField()) {
- DexField field = item.asDexEncodedField().getReference();
- includeDescriptor(item, field.type, context);
+ DexEncodedField field = item.asDexEncodedField();
+ includeDescriptor(item, field.getType(), context, precondition);
} else {
assert item.isDexClass();
}
@@ -1101,7 +1173,7 @@
DexDefinition item,
ProguardConfigurationRule context,
ProguardMemberRule rule,
- DexDefinition precondition,
+ DexProgramClass precondition,
ProguardIfRule ifRule) {
if (context instanceof ProguardKeepRule) {
if (item.isDexEncodedField()) {
@@ -1150,8 +1222,14 @@
// The reason for keeping should link to the conditional rule as a whole, if present.
ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
+
// The modifiers are specified on the actual keep rule (ie, the consequent/context).
ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
+ if (modifiers.isBottom()) {
+ // This rule is a no-op.
+ return;
+ }
+
// In compatibility mode, for a match on instance members a referenced class becomes live.
if (options.forceProguardCompatibility
&& !modifiers.allowsShrinking
@@ -1164,6 +1242,10 @@
context.markAsUsed();
}
}
+
+ // TODO(b/192636793): Remove the noShrinking and dependentNoShrinking collections. A
+ // prerequisite for this is that the ProguardKeepRule instances are added to the KeepInfo,
+ // since this is needed for the -whyareyoukeeping graph.
if (!modifiers.allowsShrinking) {
if (precondition != null) {
dependentNoShrinking
@@ -1173,27 +1255,63 @@
noShrinking.addReferenceWithRule(item.getReference(), keepRule);
}
context.markAsUsed();
- } else if (!modifiers.allowsOptimization) {
- if (precondition != null) {
- dependentSoftPinned
- .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
- .addReferenceWithRule(item.getReference(), keepRule);
- } else {
- softPinned.addReferenceWithRule(item.getReference(), keepRule);
- }
}
- if (!modifiers.allowsOptimization) {
- // The -dontoptimize flag has only effect through the keep all rule, but we still
- // need to mark the rule as used.
+
+ EnqueuerEvent preconditionEvent;
+ if (precondition != null) {
+ preconditionEvent =
+ item.getAccessFlags().isStatic()
+ ? new LiveClassEnqueuerEvent(precondition)
+ : new InstantiatedClassEnqueuerEvent(precondition);
+ } else {
+ preconditionEvent = null;
+ }
+
+ if (appView.options().isAnnotationRemovalEnabled() && !modifiers.allowsAnnotationRemoval) {
+ if (precondition != null) {
+ modifyDependentMinimumKeepInfo(
+ dependentMinimumKeepInfo,
+ preconditionEvent,
+ item,
+ Joiner::disallowAnnotationRemoval);
+ } else {
+ modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowAnnotationRemoval);
+ }
context.markAsUsed();
}
if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
- noObfuscation.add(item.getReference());
+ if (precondition != null) {
+ modifyDependentMinimumKeepInfo(
+ dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowMinification);
+ } else {
+ modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowMinification);
+ }
context.markAsUsed();
}
+
+ if (appView.options().isOptimizationEnabled() && !modifiers.allowsOptimization) {
+ if (precondition != null) {
+ modifyDependentMinimumKeepInfo(
+ dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowOptimization);
+ } else {
+ modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowOptimization);
+ }
+ context.markAsUsed();
+ }
+
+ if (appView.options().isShrinking() && !modifiers.allowsShrinking) {
+ if (precondition != null) {
+ modifyDependentMinimumKeepInfo(
+ dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowShrinking);
+ } else {
+ modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowShrinking);
+ }
+ context.markAsUsed();
+ }
+
if (modifiers.includeDescriptorClasses) {
- includeDescriptorClasses(item, keepRule);
+ includeDescriptorClasses(item, keepRule, precondition);
context.markAsUsed();
}
} else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
@@ -1409,10 +1527,9 @@
final Set<DexMethod> neverInlineDueToSingleCaller;
final Set<DexType> neverClassInline;
final MutableItemsWithRules noShrinking;
- final MutableItemsWithRules softPinned;
- final Set<DexReference> noObfuscation;
+ final Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo;
+ final Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo;
final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
- final Map<DexReference, MutableItemsWithRules> dependentSoftPinned;
final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
final List<DelayedRootSetActionItem> delayedRootSetActionItems;
@@ -1421,20 +1538,18 @@
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexType> neverClassInline,
MutableItemsWithRules noShrinking,
- MutableItemsWithRules softPinned,
- Set<DexReference> noObfuscation,
+ Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+ Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
- Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
this.neverInline = neverInline;
this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
this.neverClassInline = neverClassInline;
this.noShrinking = noShrinking;
- this.softPinned = softPinned;
- this.noObfuscation = noObfuscation;
+ this.minimumKeepInfo = minimumKeepInfo;
+ this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
this.dependentNoShrinking = dependentNoShrinking;
- this.dependentSoftPinned = dependentSoftPinned;
this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
this.delayedRootSetActionItems = delayedRootSetActionItems;
}
@@ -1543,6 +1658,54 @@
Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
return dependentKeepClassCompatRule.get(type);
}
+
+ public void forEachMinimumKeepInfo(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ TriConsumer<EnqueuerEvent, DexProgramClass, KeepClassInfo.Joiner> classConsumer,
+ TriConsumer<EnqueuerEvent, ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
+ TriConsumer<EnqueuerEvent, ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
+ internalForEachMinimumKeepInfo(
+ appView, minimumKeepInfo, null, classConsumer, fieldConsumer, methodConsumer);
+ dependentMinimumKeepInfo.forEach(
+ (precondition, minimumKeepInfoForDependents) ->
+ internalForEachMinimumKeepInfo(
+ appView,
+ minimumKeepInfoForDependents,
+ precondition,
+ classConsumer,
+ fieldConsumer,
+ methodConsumer));
+ }
+
+ private static void internalForEachMinimumKeepInfo(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+ EnqueuerEvent precondition,
+ TriConsumer<EnqueuerEvent, DexProgramClass, KeepClassInfo.Joiner> classConsumer,
+ TriConsumer<EnqueuerEvent, ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
+ TriConsumer<EnqueuerEvent, ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
+ minimumKeepInfo.forEach(
+ (reference, joiner) -> {
+ DexProgramClass contextClass =
+ asProgramClassOrNull(appView.definitionFor(reference.getContextType()));
+ if (contextClass != null) {
+ reference.accept(
+ clazz -> classConsumer.accept(precondition, contextClass, joiner.asClassJoiner()),
+ fieldReference -> {
+ ProgramField field = contextClass.lookupProgramField(fieldReference);
+ if (field != null) {
+ fieldConsumer.accept(precondition, field, joiner.asFieldJoiner());
+ }
+ },
+ methodReference -> {
+ ProgramMethod method = contextClass.lookupProgramMethod(methodReference);
+ if (method != null) {
+ methodConsumer.accept(precondition, method, joiner.asMethodJoiner());
+ }
+ });
+ }
+ });
+ }
}
abstract static class ItemsWithRules {
@@ -1596,8 +1759,7 @@
static class MutableItemsWithRules extends ItemsWithRules {
private static final ItemsWithRules EMPTY =
- new MutableItemsWithRules(
- Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
+ new MutableItemsWithRules(emptyMap(), emptyMap(), emptyMap());
final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
@@ -1834,8 +1996,8 @@
private RootSet(
MutableItemsWithRules noShrinking,
- MutableItemsWithRules softPinned,
- Set<DexReference> noObfuscation,
+ Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+ Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
ImmutableList<DexReference> reasonAsked,
ImmutableList<DexReference> checkDiscarded,
Set<DexMethod> alwaysInline,
@@ -1858,7 +2020,6 @@
Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
- Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules,
@@ -1868,10 +2029,9 @@
neverInlineDueToSingleCaller,
neverClassInline,
noShrinking,
- softPinned,
- noObfuscation,
+ minimumKeepInfo,
+ dependentMinimumKeepInfo,
dependentNoShrinking,
- dependentSoftPinned,
dependentKeepClassCompatRule,
delayedRootSetActionItems);
this.reasonAsked = reasonAsked;
@@ -1916,12 +2076,10 @@
neverInline.addAll(consequentRootSet.neverInline);
neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
neverClassInline.addAll(consequentRootSet.neverClassInline);
- noObfuscation.addAll(consequentRootSet.noObfuscation);
if (addNoShrinking) {
noShrinking.addAll(consequentRootSet.noShrinking);
}
addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
- addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned);
consequentRootSet.dependentKeepClassCompatRule.forEach(
(type, rules) ->
dependentKeepClassCompatRule
@@ -1941,32 +2099,6 @@
.putAll(dependence));
}
- public void copy(DexReference original, DexReference rewritten) {
- if (noShrinking.containsReference(original)) {
- noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
- }
- if (noObfuscation.contains(original)) {
- noObfuscation.add(rewritten);
- }
- if (original.isDexMember()) {
- assert rewritten.isDexMember();
- DexMember<?, ?> originalMember = original.asDexMember();
- if (noSideEffects.containsKey(originalMember)) {
- noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember));
- }
- if (assumedValues.containsKey(originalMember)) {
- assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember));
- }
- }
- }
-
- public void prune(DexReference reference) {
- noShrinking.removeReference(reference);
- noObfuscation.remove(reference);
- noSideEffects.remove(reference);
- assumedValues.remove(reference);
- }
-
public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
@@ -2007,13 +2139,8 @@
});
}
- public void move(DexReference original, DexReference rewritten) {
- copy(original, rewritten);
- prune(original);
- }
-
void shouldNotBeMinified(DexReference reference) {
- noObfuscation.add(reference);
+ modifyMinimumKeepInfo(minimumKeepInfo, reference, Joiner::disallowMinification);
}
public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
@@ -2154,7 +2281,6 @@
StringBuilder builder = new StringBuilder();
builder.append("RootSet");
builder.append("\nnoShrinking: " + noShrinking.size());
- builder.append("\nnoObfuscation: " + noObfuscation.size());
builder.append("\nreasonAsked: " + reasonAsked.size());
builder.append("\ncheckDiscarded: " + checkDiscarded.size());
builder.append("\nnoSideEffects: " + noSideEffects.size());
@@ -2209,10 +2335,9 @@
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexType> neverClassInline,
MutableItemsWithRules noShrinking,
- MutableItemsWithRules softPinned,
- Set<DexReference> noObfuscation,
+ Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+ Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
- Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
super(
@@ -2220,10 +2345,9 @@
neverInlineDueToSingleCaller,
neverClassInline,
noShrinking,
- softPinned,
- noObfuscation,
+ minimumKeepInfo,
+ dependentMinimumKeepInfo,
dependentNoShrinking,
- dependentSoftPinned,
dependentKeepClassCompatRule,
delayedRootSetActionItems);
}
@@ -2275,8 +2399,8 @@
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
super(
noShrinking,
- new MutableItemsWithRules(),
- Collections.emptySet(),
+ emptyMap(),
+ emptyMap(),
reasonAsked,
checkDiscarded,
Collections.emptySet(),
@@ -2295,12 +2419,11 @@
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
- Collections.emptyMap(),
- Collections.emptyMap(),
- Collections.emptyMap(),
+ emptyMap(),
+ emptyMap(),
+ emptyMap(),
dependentNoShrinking,
- Collections.emptyMap(),
- Collections.emptyMap(),
+ emptyMap(),
Collections.emptySet(),
ifRules,
delayedRootSetActionItems);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 6f390ea..061eb02 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -29,7 +29,16 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature.ClassSignatureBuilder;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
+import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
@@ -56,6 +65,7 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
@@ -302,6 +312,7 @@
}
// The set of targets that must remain for proper resolution error cases should not be merged.
+ // TODO(b/192821424): Can be removed if handled.
for (DexMethod method : appInfo.getFailedMethodResolutionTargets()) {
markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
}
@@ -1078,6 +1089,9 @@
return false;
}
+ // Rewrite generic signatures before we merge fields.
+ rewriteGenericSignatures(target, source, directMethods.values(), virtualMethods.values());
+
// Step 2: Merge fields
Set<DexString> existingFieldNames = new HashSet<>();
for (DexEncodedField field : target.fields()) {
@@ -1138,9 +1152,148 @@
// Step 4: Record merging.
mergedClasses.put(source.type, target.type);
assert !abortMerge;
+ assert GenericSignatureCorrectnessHelper.createForVerification(
+ appView, GenericSignatureContextBuilder.createForSingleClass(appView, target))
+ .evaluateSignaturesForClass(target)
+ .isValid();
return true;
}
+ /**
+ * The rewriting of generic signatures is pretty simple, but require some bookkeeping. We take
+ * the arguments to the base type:
+ *
+ * <pre>
+ * class Sub<X> extends Base<X, String>
+ * </pre>
+ *
+ * for
+ *
+ * <pre>
+ * class Base<T,R> extends OtherBase<T> implements I<R> {
+ * T t() { ... };
+ * }
+ * </pre>
+ *
+ * and substitute T -> X and R -> String
+ */
+ private void rewriteGenericSignatures(
+ DexProgramClass target,
+ DexProgramClass source,
+ Collection<DexEncodedMethod> directMethods,
+ Collection<DexEncodedMethod> virtualMethods) {
+ ClassSignature targetSignature = target.getClassSignature();
+ if (targetSignature.hasNoSignature()) {
+ // Null out all source signatures that is moved, but do not clear out the class since this
+ // could be referred to by other generic signatures.
+ // TODO(b/147504070): If merging classes with enclosing/innerclasses, this needs to be
+ // reconsidered.
+ directMethods.forEach(DexEncodedMethod::clearGenericSignature);
+ virtualMethods.forEach(DexEncodedMethod::clearGenericSignature);
+ source.fields().forEach(DexEncodedMember::clearGenericSignature);
+ return;
+ }
+ GenericSignaturePartialTypeArgumentApplier classApplier =
+ getGenericSignatureArgumentApplier(target, source);
+ if (classApplier == null) {
+ target.clearClassSignature();
+ target.members().forEach(DexEncodedMember::clearGenericSignature);
+ return;
+ }
+ // We could generate a substitution map.
+ ClassSignature rewrittenSource = classApplier.visitClassSignature(source.getClassSignature());
+ // The variables in the class signature is now rewritten to use the targets argument.
+ ClassSignatureBuilder builder = ClassSignature.builder();
+ builder.addFormalTypeParameters(targetSignature.getFormalTypeParameters());
+ if (!source.isInterface()) {
+ if (rewrittenSource.hasSignature()) {
+ builder.setSuperClassSignature(rewrittenSource.superClassSignature());
+ } else {
+ builder.setSuperClassSignature(new ClassTypeSignature(source.superType));
+ }
+ } else {
+ builder.setSuperClassSignature(targetSignature.superClassSignature());
+ }
+ // Compute the seen set for interfaces to add. This is similar to the merging of interfaces
+ // but allow us to maintain the type arguments.
+ Set<DexType> seenInterfaces = new HashSet<>();
+ if (source.isInterface()) {
+ seenInterfaces.add(source.type);
+ }
+ for (ClassTypeSignature iFace : targetSignature.superInterfaceSignatures()) {
+ if (seenInterfaces.add(iFace.type())) {
+ builder.addInterface(iFace);
+ }
+ }
+ if (rewrittenSource.hasSignature()) {
+ for (ClassTypeSignature iFace : rewrittenSource.superInterfaceSignatures()) {
+ if (!seenInterfaces.contains(iFace.type())) {
+ builder.addInterface(iFace);
+ }
+ }
+ } else {
+ // Synthesize raw uses of interfaces to align with the actual class
+ for (DexType iFace : source.interfaces) {
+ if (!seenInterfaces.contains(iFace)) {
+ builder.addInterface(new ClassTypeSignature(iFace));
+ }
+ }
+ }
+ target.setClassSignature(builder.build());
+
+ // Go through all type-variable references for members and update them.
+ CollectionUtils.forEach(
+ method -> {
+ MethodTypeSignature methodSignature = method.getGenericSignature();
+ if (methodSignature.hasNoSignature()) {
+ return;
+ }
+ method.setGenericSignature(
+ classApplier
+ .buildForMethod(methodSignature.getFormalTypeParameters())
+ .visitMethodSignature(methodSignature));
+ },
+ directMethods,
+ virtualMethods);
+
+ source.forEachField(
+ field -> {
+ if (field.getGenericSignature().hasNoSignature()) {
+ return;
+ }
+ field.setGenericSignature(
+ classApplier.visitFieldTypeSignature(field.getGenericSignature()));
+ });
+ }
+
+ private GenericSignaturePartialTypeArgumentApplier getGenericSignatureArgumentApplier(
+ DexProgramClass target, DexProgramClass source) {
+ assert target.getClassSignature().hasSignature();
+ // We can assert proper structure below because the generic signature validator has run
+ // before and pruned invalid signatures.
+ List<FieldTypeSignature> genericArgumentsToSuperType =
+ target.getClassSignature().getGenericArgumentsToSuperType(source.type);
+ if (genericArgumentsToSuperType == null) {
+ assert false : "Type should be present in generic signature";
+ return null;
+ }
+ List<FormalTypeParameter> formals = source.getClassSignature().getFormalTypeParameters();
+ if (genericArgumentsToSuperType.size() != formals.size()) {
+ assert false : "Invalid argument count to formals";
+ return null;
+ }
+ Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
+ for (int i = 0; i < formals.size(); i++) {
+ // It is OK to override a generic type variable so we just use put.
+ substitutionMap.put(formals.get(i).getName(), genericArgumentsToSuperType.get(i));
+ }
+ return GenericSignaturePartialTypeArgumentApplier.build(
+ appView,
+ TypeParameterContext.empty().addPrunedSubstitutions(substitutionMap),
+ (type1, type2) -> true,
+ type -> true);
+ }
+
private boolean restoreDebuggingState(Stream<DexEncodedMethod> toBeDiscarded) {
toBeDiscarded.forEach(
method -> {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index 467c168..f2732f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -122,7 +122,7 @@
}
}
}
- DexMethod newMethod = methodMap.get(previous.getReference());
+ DexMethod newMethod = methodMap.apply(previous.getReference());
if (newMethod == null) {
return previous;
}
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 f9bdee5..d187792 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -319,6 +319,9 @@
assert verifyAllHaveSameFeature(types, LegacySyntheticReference::getFeatureSplit);
return types.get(0).getFeatureSplit();
}
+ if (isSyntheticOfKind(type, SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
+ return FeatureSplit.BASE;
+ }
List<SynthesizingContext> contexts = getSynthesizingContexts(type);
if (contexts.isEmpty()) {
return null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index edee559..9f87beb 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -25,7 +25,8 @@
*/
public enum SyntheticKind {
// Class synthetics.
- ENUM_UNBOXING_SHARED_UTILITY_CLASS("$EnumUnboxingSharedUtility", 24, false, true),
+ ENUM_UNBOXING_LOCAL_UTILITY_CLASS("$EnumUnboxingLocalUtility", 24, false, true),
+ ENUM_UNBOXING_SHARED_UTILITY_CLASS("$EnumUnboxingSharedUtility", 25, false, true),
RECORD_TAG("", 1, false, true, true),
COMPANION_CLASS("$-CC", 2, false, true),
EMULATED_INTERFACE_CLASS("$-EL", 3, false, true),
@@ -39,6 +40,7 @@
HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
// Method synthetics.
+ ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
RECORD_HELPER("Record", 9, true),
BACKPORT("Backport", 10, true),
STATIC_INTERFACE_CALL("StaticInterfaceCall", 11, true),
@@ -49,7 +51,8 @@
THROW_NSME("ThrowNSME", 16, true),
TWR_CLOSE_RESOURCE("TwrCloseResource", 17, true),
SERVICE_LOADER("ServiceLoad", 18, true),
- OUTLINE("Outline", 19, true);
+ OUTLINE("Outline", 19, true),
+ API_CONVERSION("APIConversion", 26, true);
static {
assert verifyNoOverlappingIds();
diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
index 6584f77..91b0638 100644
--- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
@@ -7,12 +7,21 @@
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.Set;
+import java.util.function.Consumer;
public class CollectionUtils {
+
public static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
ImmutableSet.Builder<T> builder = ImmutableSet.builder();
builder.addAll(first);
builder.addAll(second);
return builder.build();
}
+
+ @SafeVarargs
+ public static <T> void forEach(Consumer<T> consumer, Collection<T>... collections) {
+ for (Collection<T> collection : collections) {
+ collection.forEach(consumer);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ImmutableArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ImmutableArrayUtils.java
new file mode 100644
index 0000000..d7c7950
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ImmutableArrayUtils.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;
+
+public class ImmutableArrayUtils {
+
+ public static <T> T[] set(T[] array, int index, T element) {
+ T[] clone = array.clone();
+ clone[index] = element;
+ return clone;
+ }
+}
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 e82e04d..c17a6ac 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -102,6 +102,9 @@
// This makes life easier when running R8 in a debugger.
public static final boolean DETERMINISTIC_DEBUGGING = false;
+ // Use a MethodCollection where most interleavings between reading and mutating is caught.
+ public static final boolean USE_METHOD_COLLECTION_CONCURRENCY_CHECKED = false;
+
public enum LineNumberOptimization {
OFF,
ON
@@ -223,6 +226,7 @@
enableValuePropagation = false;
enableSideEffectAnalysis = false;
enableTreeShakingOfLibraryMethodOverrides = false;
+ enableInitializedClassesAnalysis = false;
callSiteOptimizationOptions.disableOptimization();
horizontalClassMergerOptions.setRestrictToSynthetics();
}
@@ -584,6 +588,11 @@
}
@Override
+ public boolean isAnnotationRemovalEnabled() {
+ return !isForceProguardCompatibilityEnabled();
+ }
+
+ @Override
public boolean isTreeShakingEnabled() {
return isShrinking();
}
@@ -594,6 +603,11 @@
}
@Override
+ public boolean isOptimizationEnabled() {
+ return isOptimizing();
+ }
+
+ @Override
public boolean isRepackagingEnabled() {
return proguardConfiguration.getPackageObfuscationMode().isSome()
&& (isMinifying() || testing.repackageWithNoMinification);
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 89063c4..68a99e3 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -62,10 +62,11 @@
return Collections.unmodifiableList(list);
}
- public static <T> T findOrDefault(Iterable<T> iterable, Predicate<T> predicate, T defaultValue) {
+ public static <T, R extends T> R findOrDefault(
+ Iterable<T> iterable, Predicate<T> predicate, R defaultValue) {
for (T element : iterable) {
if (predicate.test(element)) {
- return element;
+ return (R) element;
}
}
return defaultValue;
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 3ecdc4f..9420440 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -155,6 +155,12 @@
removeIf((Iterator<Instruction>) iterator, predicate);
}
+ public static void skip(InstructionIterator iterator, int times) {
+ for (int i = 0; i < times; i++) {
+ iterator.next();
+ }
+ }
+
public static <T> boolean allRemainingMatch(ListIterator<T> iterator, Predicate<T> predicate) {
return !anyRemainingMatch(iterator, remaining -> !predicate.test(remaining));
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 5781f91..91bfbe6 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -48,6 +48,17 @@
return result != null ? result : defaultValue;
}
+ public static <T> List<T> filter(List<T> list, Predicate<? super T> predicate) {
+ ArrayList<T> filtered = new ArrayList<>(list.size());
+ list.forEach(
+ t -> {
+ if (predicate.test(t)) {
+ filtered.add(t);
+ }
+ });
+ return filtered;
+ }
+
public static <T> T first(List<T> list) {
return list.get(0);
}
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index fc39146..2460a3c 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -32,25 +32,6 @@
return ignore -> supplier.get();
}
- public static <K, V> Map<K, V> map(
- Map<K, V> map,
- IntFunction<Map<K, V>> factory,
- Function<K, K> keyMapping,
- Function<V, V> valueMapping,
- BiFunction<V, V, V> valueMerger) {
- Map<K, V> result = factory.apply(map.size());
- map.forEach(
- (key, value) -> {
- K newKey = keyMapping.apply(key);
- V newValue = valueMapping.apply(value);
- V existingValue = result.put(newKey, newValue);
- if (existingValue != null) {
- result.put(newKey, valueMerger.apply(existingValue, newValue));
- }
- });
- return result;
- }
-
public static <K, V> IdentityHashMap<K, V> newIdentityHashMap(BiForEachable<K, V> forEachable) {
IdentityHashMap<K, V> map = new IdentityHashMap<>();
forEachable.forEach(map::put);
@@ -65,4 +46,26 @@
return StringUtils.join(
",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
}
+
+ public static <K1, V1, K2, V2> Map<K2, V2> transform(
+ Map<K1, V1> map,
+ IntFunction<Map<K2, V2>> factory,
+ Function<K1, K2> keyMapping,
+ Function<V1, V2> valueMapping,
+ BiFunction<V2, V2, V2> valueMerger) {
+ Map<K2, V2> result = factory.apply(map.size());
+ map.forEach(
+ (key, value) -> {
+ K2 newKey = keyMapping.apply(key);
+ if (newKey == null) {
+ return;
+ }
+ V2 newValue = valueMapping.apply(value);
+ V2 existingValue = result.put(newKey, newValue);
+ if (existingValue != null) {
+ result.put(newKey, valueMerger.apply(existingValue, newValue));
+ }
+ });
+ return result;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java b/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java
new file mode 100644
index 0000000..193df46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java
@@ -0,0 +1,29 @@
+// 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.utils;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.google.common.base.Equivalence;
+
+public class ProgramFieldEquivalence extends Equivalence<ProgramField> {
+
+ private static final ProgramFieldEquivalence INSTANCE = new ProgramFieldEquivalence();
+
+ private ProgramFieldEquivalence() {}
+
+ public static ProgramFieldEquivalence get() {
+ return INSTANCE;
+ }
+
+ @Override
+ protected boolean doEquivalent(ProgramField field, ProgramField other) {
+ return field.getDefinition() == other.getDefinition();
+ }
+
+ @Override
+ protected int doHash(ProgramField field) {
+ return field.getReference().hashCode();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 070e63f..01b58e7 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -13,6 +13,15 @@
public class SetUtils {
+ public static <T> boolean containsAnyOf(Set<T> set, Iterable<T> elements) {
+ for (T element : elements) {
+ if (set.contains(element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static <T> Set<T> newIdentityHashSet(T element) {
Set<T> result = Sets.newIdentityHashSet();
result.add(element);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
index 66b2375..ec8f2ad 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
@@ -78,6 +78,11 @@
}
@Override
+ public K getKeyOrDefault(V value, K defaultValue) {
+ return inverse.getOrDefault(value, defaultValue);
+ }
+
+ @Override
public Set<K> getKeys(V value) {
K key = inverse.get(value);
return key != null ? Collections.singleton(key) : Collections.emptySet();
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java
index addb1ed..4353088 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java
@@ -22,4 +22,6 @@
Set<V> getOrDefault(Object key, Set<V> defaultValue);
K getKey(V value);
+
+ K getKeyOrDefault(V value, K defaultValue);
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
new file mode 100644
index 0000000..24f7e05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
@@ -0,0 +1,28 @@
+// 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.utils.collections;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.utils.ProgramFieldEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class ProgramFieldMap<V> extends ProgramMemberMap<ProgramField, V> {
+
+ private ProgramFieldMap(Supplier<Map<Wrapper<ProgramField>, V>> backingFactory) {
+ super(backingFactory);
+ }
+
+ public static <V> ProgramFieldMap<V> create() {
+ return new ProgramFieldMap<>(HashMap::new);
+ }
+
+ @Override
+ Wrapper<ProgramField> wrap(ProgramField method) {
+ return ProgramFieldEquivalence.get().wrap(method);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
new file mode 100644
index 0000000..43daf74
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -0,0 +1,57 @@
+// 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.utils.collections;
+
+import com.android.tools.r8.graph.ProgramMember;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class ProgramMemberMap<K extends ProgramMember<?, ?>, V> {
+
+ private final Map<Wrapper<K>, V> backing;
+
+ ProgramMemberMap(Supplier<Map<Wrapper<K>, V>> backingFactory) {
+ backing = backingFactory.get();
+ }
+
+ public void clear() {
+ backing.clear();
+ }
+
+ public V computeIfAbsent(K member, Function<K, V> fn) {
+ return backing.computeIfAbsent(wrap(member), key -> fn.apply(key.get()));
+ }
+
+ public void forEach(BiConsumer<K, V> consumer) {
+ backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
+ }
+
+ public V get(K member) {
+ return backing.get(wrap(member));
+ }
+
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ public V put(K member, V value) {
+ Wrapper<K> wrapper = wrap(member);
+ return backing.put(wrapper, value);
+ }
+
+ public V remove(K member) {
+ return backing.remove(wrap(member));
+ }
+
+ public void removeIf(BiPredicate<K, V> predicate) {
+ backing.entrySet().removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue()));
+ }
+
+ abstract Wrapper<K> wrap(K member);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
index d49928a..a3e1b6b 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
@@ -8,50 +8,26 @@
import com.android.tools.r8.utils.ProgramMethodEquivalence;
import com.google.common.base.Equivalence.Wrapper;
import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.Map;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
-public class ProgramMethodMap<V> {
-
- private final Map<Wrapper<ProgramMethod>, V> backing;
+public class ProgramMethodMap<V> extends ProgramMemberMap<ProgramMethod, V> {
private ProgramMethodMap(Supplier<Map<Wrapper<ProgramMethod>, V>> backingFactory) {
- backing = backingFactory.get();
+ super(backingFactory);
}
public static <V> ProgramMethodMap<V> create() {
return new ProgramMethodMap<>(HashMap::new);
}
- public static <V> ProgramMethodMap<V> createLinked() {
- return new ProgramMethodMap<>(LinkedHashMap::new);
+ public static <V> ProgramMethodMap<V> createConcurrent() {
+ return new ProgramMethodMap<>(ConcurrentHashMap::new);
}
- public void clear() {
- backing.clear();
- }
-
- public V computeIfAbsent(ProgramMethod method, Function<ProgramMethod, V> fn) {
- return backing.computeIfAbsent(wrap(method), key -> fn.apply(key.get()));
- }
-
- public void forEach(BiConsumer<ProgramMethod, V> consumer) {
- backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
- }
-
- public boolean isEmpty() {
- return backing.isEmpty();
- }
-
- public V put(ProgramMethod method, V value) {
- Wrapper<ProgramMethod> wrapper = ProgramMethodEquivalence.get().wrap(method);
- return backing.put(wrapper, value);
- }
-
- private static Wrapper<ProgramMethod> wrap(ProgramMethod method) {
+ @Override
+ Wrapper<ProgramMethod> wrap(ProgramMethod method) {
return ProgramMethodEquivalence.get().wrap(method);
}
}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index ec2b927..468c1e9 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -31,7 +31,7 @@
public enum KotlinCompilerVersion {
KOTLINC_1_3_72("kotlin-compiler-1.3.72"),
KOTLINC_1_4_20("kotlin-compiler-1.4.20"),
- KOTLINC_1_5_0_M2("kotlin-compiler-1.5.0-M2");
+ KOTLINC_1_5_0("kotlin-compiler-1.5.0");
private final String folder;
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
index 57658e1..7cfcc07 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
@@ -93,7 +93,7 @@
// KotlinTargetVersion java 6 is deprecated from kotlinc 1.5 and forward, no need to run
// tests on that target.
if (targetVersion != KotlinTargetVersion.JAVA_6
- || kotlinc.isNot(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+ || kotlinc.isNot(KotlinCompilerVersion.KOTLINC_1_5_0)) {
testParameters.add(new KotlinTestParameters(kotlinc, targetVersion, index++));
}
}
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
index 737b0ff..396e9e0 100644
--- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
+++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -187,6 +187,10 @@
return new Builder();
}
+ public static LibraryDesugaringTestConfiguration forApiLevel(AndroidApiLevel apiLevel) {
+ return LibraryDesugaringTestConfiguration.builder().setMinApi(apiLevel).build();
+ }
+
public boolean isEnabled() {
return this != DISABLED;
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 211df9c..1e1b55f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -436,10 +436,6 @@
return allowStderrMessages();
}
- public T enableCoreLibraryDesugaring(AndroidApiLevel minAPILevel) {
- return enableCoreLibraryDesugaring(minAPILevel, null);
- }
-
public T enableCoreLibraryDesugaring(
AndroidApiLevel minApiLevel, StringConsumer keepRuleConsumer) {
return enableCoreLibraryDesugaring(
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f3108e5..0a7edb3 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -5,7 +5,7 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
@@ -124,8 +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 ASM_JAR = BUILD_DIR + "deps/asm-9.0.jar";
- public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.0.jar";
+ 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";
public static final Path API_SAMPLE_JAR = Paths.get("tests", "r8_api_usage_sample.jar");
@@ -2169,12 +2169,12 @@
return new KotlinCompiler(KOTLINC_1_4_20);
}
- public static KotlinCompiler getKotlinC_1_5_0_m2() {
- return new KotlinCompiler(KOTLINC_1_5_0_M2);
+ public static KotlinCompiler getKotlinC_1_5_0() {
+ return new KotlinCompiler(KOTLINC_1_5_0);
}
public static KotlinCompiler[] getKotlinCompilers() {
- return new KotlinCompiler[] {getKotlinC_1_3_72(), getKotlinC_1_4_20(), getKotlinC_1_5_0_m2()};
+ return new KotlinCompiler[] {getKotlinC_1_3_72(), getKotlinC_1_4_20(), getKotlinC_1_5_0()};
}
public static void disassemble(AndroidApp app, PrintStream ps) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
index 38f7b45..c3c1940 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
@@ -41,10 +41,10 @@
@Test
public void testClockR8() throws Exception {
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
- Method printZone = CustomLibClass.class.getDeclaredMethod("printZone");
+ Method printZone = DesugaredLibUser.class.getDeclaredMethod("printZone");
Method main = Executor.class.getDeclaredMethod("main", String[].class);
testForR8(parameters.getBackend())
- .addProgramClasses(Executor.class, CustomLibClass.class)
+ .addProgramClasses(Executor.class, DesugaredLibUser.class)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addKeepMainRule(Executor.class)
.setMinApi(parameters.getApiLevel())
@@ -54,8 +54,7 @@
ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
(reference, apiLevel) -> {
if (reference.equals(Reference.methodFromMethod(printZone))) {
- // TODO(b/191617445): This should probably always be parameters.getApiLevel()
- assertEquals(AndroidApiLevel.O.max(parameters.getApiLevel()), apiLevel);
+ assertEquals(parameters.getApiLevel(), apiLevel);
}
}))
.compile()
@@ -66,20 +65,17 @@
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Executor.class)
.assertSuccessWithOutputLines("Z")
- // TODO(b/191617445): We should always be able to inline
- .inspect(
- ApiModelingTestHelper.verifyThat(parameters, printZone)
- .inlinedIntoFromApiLevel(main, AndroidApiLevel.O));
+ .inspect(ApiModelingTestHelper.verifyThat(parameters, printZone).inlinedInto(main));
}
static class Executor {
public static void main(String[] args) {
- CustomLibClass.printZone();
+ DesugaredLibUser.printZone();
}
}
- static class CustomLibClass {
+ static class DesugaredLibUser {
public static void printZone() {
System.out.println(Clock.systemUTC().getZone());
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java
new file mode 100644
index 0000000..e752764
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java
@@ -0,0 +1,93 @@
+// 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 org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import java.nio.file.Paths;
+import java.util.List;
+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 ApiModelNoDesugaredLibraryReferenceTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public ApiModelNoDesugaredLibraryReferenceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ Method printZone = LibUser.class.getDeclaredMethod("printPath");
+ Method main = Executor.class.getDeclaredMethod("main", String[].class);
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Executor.class, LibUser.class)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addKeepMainRule(Executor.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(
+ ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
+ (reference, apiLevel) -> {
+ if (reference.equals(Reference.methodFromMethod(printZone))) {
+ assertEquals(AndroidApiLevel.O.max(parameters.getApiLevel()), apiLevel);
+ }
+ }))
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel(), keepRuleConsumer.get(), false)
+ .run(parameters.getRuntime(), Executor.class)
+ .applyIf(
+ parameters.getDexRuntimeVersion().isDalvik(),
+ result -> result.assertSuccessWithOutputLines("java.nio.file.Paths"))
+ .applyIf(
+ parameters.getDexRuntimeVersion().isInRangeInclusive(Version.V5_1_1, Version.V7_0_0),
+ result ->
+ result.assertSuccessWithOutputLines("Failed resolution of: Ljava/nio/file/Paths;"))
+ .applyIf(
+ parameters.getDexRuntimeVersion().isNewerThan(Version.V7_0_0),
+ result -> result.assertSuccessWithOutputLines("~"))
+ .inspect(
+ ApiModelingTestHelper.verifyThat(parameters, printZone)
+ .inlinedIntoFromApiLevel(main, AndroidApiLevel.O));
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ try {
+ LibUser.printPath();
+ } catch (Throwable t) {
+ System.out.println(t.getMessage());
+ }
+ }
+ }
+
+ static class LibUser {
+
+ public static void printPath() {
+ // java.nio.Path is not (yet) library desugared.
+ System.out.println(Paths.get("~"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalMissingLookupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalMissingLookupTest.java
new file mode 100644
index 0000000..e62a40e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalMissingLookupTest.java
@@ -0,0 +1,65 @@
+// 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverInline;
+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 HorizontalMissingLookupTest extends HorizontalClassMergingTestBase {
+
+ public HorizontalMissingLookupTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(transformer(B.class).removeMethodsWithName("foo").transform())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A::baz", "B::bar");
+ }
+
+ public static class A {
+
+ @NeverInline
+ public static void baz() {
+ System.out.println("A::baz");
+ }
+ }
+
+ public static class B {
+ // Will be removed.
+ public static void foo() {
+ System.out.println("B::foo");
+ }
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("B::bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.baz();
+ if (args.length > 0) {
+ B.foo();
+ }
+ B.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
index b2010d7..f7921ff 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -43,7 +44,8 @@
.addKeepMainRule(TestClass.class)
.addVerticallyMergedClassesInspector(
inspector -> inspector.assertMergedIntoSubtype(A.class))
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java b/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java
new file mode 100644
index 0000000..380127b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java
@@ -0,0 +1,371 @@
+// 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;
+
+import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+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.cf.CfVersion;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+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.objectweb.asm.Opcodes;
+
+// Test for reproducing b/192310793.
+@RunWith(Parameterized.class)
+public class ConcurrencyTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters()
+ .withCfRuntimesEndingAtExcluding(JDK11)
+ .withDexRuntimes()
+ .withApiLevel(AndroidApiLevel.B)
+ .build());
+ }
+
+ public Collection<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class);
+ }
+
+ public Collection<byte[]> getTransformedClasses() throws Exception {
+ ClassFileTransformer transformer =
+ withNest(Host.class)
+ .setVersion(CfVersion.V1_8)
+ .transformMethodInsnInMethod(
+ "callPrivate",
+ ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.visitMethodInsn(
+ name.equals("hello") ? Opcodes.INVOKEVIRTUAL : opcode,
+ owner,
+ name,
+ descriptor,
+ isInterface);
+ }));
+ for (String s : new String[] {"a", "b", "c", "d", "e"}) {
+ for (int i = 0; i < 10; i++) {
+ transformer.setPrivate(Host.class.getDeclaredMethod(s + "0" + i));
+ }
+ }
+
+ return ImmutableList.of(
+ transformer.transform(),
+ withNest(A.class).transform(),
+ withNest(B.class).transform(),
+ withNest(C.class).transform(),
+ withNest(D.class).transform(),
+ withNest(E.class).transform());
+ }
+
+ private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
+ return transformer(clazz).setNest(Host.class, A.class, B.class, C.class, D.class, E.class);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ try {
+ testForD8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .compile();
+ } catch (CompilationFailedException e) {
+ if (e.getCause() instanceof ArrayIndexOutOfBoundsException) {
+ // TODO(b/192310793): This should not happen.
+ return;
+ }
+ throw e;
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.getBackend().isDex());
+
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .compile();
+ } catch (CompilationFailedException e) {
+ if (e.getCause() instanceof AssertionError
+ && e.getCause()
+ .getStackTrace()[0]
+ .getClassName()
+ .equals(
+ "com.android.tools.r8.ir.desugar.NonEmptyCfInstructionDesugaringCollection")) {
+ // TODO(b/192446461): This should not happen.
+ return;
+ }
+ throw e;
+ }
+ }
+
+ static class Host {
+ /* will be private */ void a00() {}
+ /* will be private */ void a01() {}
+ /* will be private */ void a02() {}
+ /* will be private */ void a03() {}
+ /* will be private */ void a04() {}
+ /* will be private */ void a05() {}
+ /* will be private */ void a06() {}
+ /* will be private */ void a07() {}
+ /* will be private */ void a08() {}
+ /* will be private */ void a09() {}
+
+ /* will be private */ void b00() {}
+ /* will be private */ void b01() {}
+ /* will be private */ void b02() {}
+ /* will be private */ void b03() {}
+ /* will be private */ void b04() {}
+ /* will be private */ void b05() {}
+ /* will be private */ void b06() {}
+ /* will be private */ void b07() {}
+ /* will be private */ void b08() {}
+ /* will be private */ void b09() {}
+
+ /* will be private */ void c00() {}
+ /* will be private */ void c01() {}
+ /* will be private */ void c02() {}
+ /* will be private */ void c03() {}
+ /* will be private */ void c04() {}
+ /* will be private */ void c05() {}
+ /* will be private */ void c06() {}
+ /* will be private */ void c07() {}
+ /* will be private */ void c08() {}
+ /* will be private */ void c09() {}
+
+ /* will be private */ void d00() {}
+ /* will be private */ void d01() {}
+ /* will be private */ void d02() {}
+ /* will be private */ void d03() {}
+ /* will be private */ void d04() {}
+ /* will be private */ void d05() {}
+ /* will be private */ void d06() {}
+ /* will be private */ void d07() {}
+ /* will be private */ void d08() {}
+ /* will be private */ void d09() {}
+
+ /* will be private */ void e00() {}
+ /* will be private */ void e01() {}
+ /* will be private */ void e02() {}
+ /* will be private */ void e03() {}
+ /* will be private */ void e04() {}
+ /* will be private */ void e05() {}
+ /* will be private */ void e06() {}
+ /* will be private */ void e07() {}
+ /* will be private */ void e08() {}
+ /* will be private */ void e09() {}
+
+ private void hello() {}
+
+ public static void callPrivate() {
+ // The private method "hello" is called with invokevirtual.
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ new Host().hello();
+ }
+ }
+
+ static class A {
+ public void foo() {
+ // Will be virtual invoke to private methods.
+ new Host().a00();
+ new Host().a01();
+ new Host().a02();
+ new Host().a03();
+ new Host().a04();
+ new Host().a05();
+ new Host().a06();
+ new Host().a07();
+ new Host().a08();
+ new Host().a09();
+ }
+ }
+
+ static class B {
+ public void foo() {
+ // Will be virtual invoke to private methods.
+ new Host().b00();
+ new Host().b01();
+ new Host().b02();
+ new Host().b03();
+ new Host().b04();
+ new Host().b05();
+ new Host().b06();
+ new Host().b07();
+ new Host().b08();
+ new Host().b09();
+ }
+ }
+
+ static class C {
+ public void foo() {
+ // Will be virtual invoke to private methods.
+ new Host().c00();
+ new Host().c01();
+ new Host().c02();
+ new Host().c03();
+ new Host().c04();
+ new Host().c05();
+ new Host().c06();
+ new Host().c07();
+ new Host().c08();
+ new Host().c09();
+ }
+ }
+
+ static class D {
+ public void foo() {
+ // Will be virtual invoke to private methods.
+ new Host().d00();
+ new Host().d01();
+ new Host().d02();
+ new Host().d03();
+ new Host().d04();
+ new Host().d05();
+ new Host().d06();
+ new Host().d07();
+ new Host().d08();
+ new Host().d09();
+ }
+ }
+
+ static class E {
+ public void foo() {
+ // Will be virtual invoke to private methods.
+ new Host().e00();
+ new Host().e01();
+ new Host().e02();
+ new Host().e03();
+ new Host().e04();
+ new Host().e05();
+ new Host().e06();
+ new Host().e07();
+ new Host().e08();
+ new Host().e09();
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new A().foo();
+ new B().foo();
+ new C().foo();
+ new D().foo();
+ new E().foo();
+ Host.callPrivate();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
index 6e82466..72cdc59 100644
--- a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
@@ -13,6 +13,7 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
@@ -72,7 +73,8 @@
rtWithoutConsumer
? ToolHelper.getAndroidJar(AndroidApiLevel.B)
: ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compileWithExpectedDiagnostics(
diagnostics -> {
if (rtWithoutConsumer) {
@@ -86,8 +88,6 @@
diagnostics.assertNoMessages();
}
})
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
assertFalse(rtWithoutConsumer);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index ddebca5..fdca331 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -126,7 +126,7 @@
Path desugaredLib =
getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
- // Run on the JVM with desuagred library on classpath.
+ // Run on the JVM with desugared library on classpath.
testForJvm()
.addProgramFiles(jar)
.addRunClasspathFiles(desugaredLib)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
index 4a4ee82..a67f335 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
@@ -82,10 +83,9 @@
.addProgramClasses(getClasses())
.addProgramClassFileData(getTransforms())
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.apply(this::checkResult);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
index e597f8e..691ef4a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
@@ -83,10 +84,9 @@
.setMinApi(parameters.getApiLevel())
.addProgramClasses(CLASSES)
.addProgramClassFileData(getTransforms())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatMatches(getExpectedError());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
index 085cd28..1f4d791 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertNotNull;
import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
@@ -69,10 +70,9 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
.addInnerClasses(DefaultMethodOverrideInLibraryTest.class)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.apply(this::checkResult);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
index 6eb1418..2cc0f87 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -10,12 +10,15 @@
import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
import java.nio.file.Path;
import java.util.Collection;
import org.junit.Test;
@@ -60,7 +63,7 @@
.addProgramFiles(libraryDex)
.addProgramClasses(TestRunner.class)
.setMinApi(apiLevel)
- .enableCoreLibraryDesugaring(apiLevel)
+ .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
.compileWithExpectedDiagnostics(
diagnostics -> {
diagnostics.assertNoInfos();
@@ -69,12 +72,8 @@
containsString(
"The compilation is slowed down due to a mix of class file and dex"
+ " file inputs in the context of desugared library.")));
- if (apiLevel.isLessThan(AndroidApiLevel.O)) {
- diagnostics.assertErrorsMatch(
- diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
- } else {
- diagnostics.assertNoMessages();
- }
+ diagnostics.assertErrorsMatch(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
});
} catch (CompilationFailedException e) {
@@ -97,7 +96,7 @@
.addProgramFiles(desugaredLibrary)
.addProgramClasses(TestRunner.class)
.setMinApi(apiLevel)
- .enableCoreLibraryDesugaring(apiLevel)
+ .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
.compile();
}
@@ -125,7 +124,7 @@
.addProgramFiles(desugaredLibraryDex)
.addProgramClasses(TestRunner.class)
.setMinApi(apiLevel)
- .enableCoreLibraryDesugaring(apiLevel)
+ .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
.compile();
}
@@ -136,7 +135,7 @@
testForD8(Backend.CF)
.addProgramClasses(Library.class)
.setMinApi(apiLevel)
- .enableCoreLibraryDesugaring(apiLevel)
+ .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
.compile()
.writeToZip();
@@ -148,13 +147,9 @@
.setMinApi(apiLevel)
.compileWithExpectedDiagnostics(
diagnostics -> {
- if (apiLevel.isLessThan(AndroidApiLevel.O)) {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
- } else {
- diagnostics.assertNoMessages();
- }
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
});
} catch (CompilationFailedException e) {
}
@@ -168,7 +163,7 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramClasses(Library.class)
.setMinApi(apiLevel)
- .enableCoreLibraryDesugaring(apiLevel)
+ .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
.compile()
.writeToZip();
@@ -187,13 +182,9 @@
.setMinApi(apiLevel)
.compileWithExpectedDiagnostics(
diagnostics -> {
- if (apiLevel.isLessThan(AndroidApiLevel.O)) {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
- } else {
- diagnostics.assertNoMessages();
- }
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
});
} catch (CompilationFailedException e) {
}
@@ -203,24 +194,30 @@
public void testMergeDifferentLibraryDesugarVersions() throws Exception {
// DEX code with library desugaring using a desugared library configuration with a
// different identifier.
- String identifier = "my-identifier";
+ Box<DesugaredLibraryConfiguration> box = new Box<>();
Path libraryDex =
testForD8(Backend.DEX)
- .applyIf(
- apiLevel.isLessThan(AndroidApiLevel.O),
- builder ->
- builder.addOptionsModification(
- options ->
- options.desugaredLibraryConfiguration =
- DesugaredLibraryConfiguration.builder(
- options.dexItemFactory(),
- options.reporter,
- Origin.unknown())
- .setDesugaredLibraryIdentifier(identifier)
- .build()))
.addProgramClasses(Library.class)
.setMinApi(apiLevel)
- .enableCoreLibraryDesugaring(apiLevel)
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.builder()
+ .setMinApi(apiLevel)
+ // Minimal configuration with a different identifier.
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromString(
+ "{"
+ + "\"configuration_format_version\":3,"
+ + "\"group_id\":\"my_group\","
+ + "\"artifact_id\":\"my_artifact\","
+ + "\"version\":\"1.0.9\","
+ + "\"synthesized_library_classes_package_prefix\":\"my_prefix\","
+ + "\"required_compilation_api_level\":\"30\","
+ + "\"common_flags\":[],"
+ + "\"library_flags\":[],"
+ + "\"program_flags\":[]"
+ + "}",
+ Origin.unknown()))
+ .build())
.compile()
.writeToZip();
@@ -239,15 +236,11 @@
.setMinApi(apiLevel)
.compileWithExpectedDiagnostics(
diagnostics -> {
- if (apiLevel.isLessThan(AndroidApiLevel.O)) {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticType(DesugaredLibraryMismatchDiagnostic.class),
- diagnosticMessage(containsString(identifier))));
- } else {
- diagnostics.assertNoMessages();
- }
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class),
+ diagnosticMessage(containsString("my_group:my_artifact:1.0.9"))));
});
} catch (CompilationFailedException e) {
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
index a3c1e0f..f686167 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -48,7 +49,8 @@
.addInnerClasses(DisableDesugarTest.class)
.setMinApi(parameters.getApiLevel())
.disableDesugaring()
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
.compileWithExpectedDiagnostics(this::checkExpectedDiagnostics);
} catch (CompilationFailedException e) {
// Expected compilation failed.
@@ -65,7 +67,8 @@
.addKeepMainRule(TestClass.class)
.setMinApi(parameters.getApiLevel())
.disableDesugaring()
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
.compileWithExpectedDiagnostics(this::checkExpectedDiagnostics);
} catch (CompilationFailedException e) {
// Expected compilation failed.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java
index 8ab1e6b..0737de9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java
@@ -7,6 +7,7 @@
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.StringContains.containsString;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -40,11 +41,10 @@
testForD8()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramClasses(executor)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.setMinApi(parameters.getApiLevel())
.compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), executor)
.assertSuccess()
// Verification error on some Dalvik VMs (4,api 1;4,api 15;4.4,api 1).
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
index e5f51dd..59a6b5b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -6,6 +6,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -70,7 +71,8 @@
testForD8(Backend.CF)
.addInnerClasses(IterableTest.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
.inspect(this::inspect)
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java
index 0726e49..86ef0d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -46,7 +47,8 @@
testForD8(Backend.CF)
.addInnerClasses(IterateTest.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
index 1e145c4..ccac378 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
@@ -9,6 +9,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -82,10 +83,10 @@
.addInnerClasses(JavaUtilOptionalTest.class)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
.inspect(this::checkRewrittenInvokes)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
}
@@ -97,9 +98,9 @@
.addProgramFiles(
Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION))
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), "backport.OptionalBackportJava9Main")
.assertSuccess();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
index ed4b6bc..d551a20 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -37,10 +38,9 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addInnerClasses(LinkedHashSetTest.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Executor.class)
.assertSuccess()
.getStdOut();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index 1f1b593..dd89992 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.ExtractMarker;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -66,7 +67,11 @@
.addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .applyIf(
+ someLibraryDesugaringRequired(),
+ b ->
+ b.enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
.compileWithExpectedDiagnostics(this::assertError);
assertFalse(expectError());
} catch (CompilationFailedException e) {
@@ -75,8 +80,6 @@
}
assert !expectError();
assert compileResult != null;
- compileResult.addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel());
compileResult
.run(parameters.getRuntime(), Part1.class)
.assertSuccessWithOutputLines(JAVA_RESULT);
@@ -110,7 +113,11 @@
.addProgramFiles(buildPart1DesugaredLibrary(), shrunkenLib)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .applyIf(
+ someLibraryDesugaringRequired(),
+ b ->
+ b.enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
.compile()
.writeToZip();
@@ -217,11 +224,13 @@
.addProgramClasses(Part2.class)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .applyIf(
+ someLibraryDesugaringRequired(),
+ b ->
+ b.enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
.compile()
- .inspectDiagnosticMessages(this::assertWarningPresent)
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel());
+ .inspectDiagnosticMessages(this::assertWarningPresent);
if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
compileResult
.run(parameters.getRuntime(), Part1.class)
@@ -240,7 +249,7 @@
}
private void assertWarningPresent(TestDiagnosticMessages testDiagnosticMessages) {
- if (parameters.getApiLevel().getLevel() > AndroidApiLevel.N.getLevel()) {
+ if (!someLibraryDesugaringRequired()) {
return;
}
assertTrue(
@@ -254,7 +263,11 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramClasses(Part1.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .applyIf(
+ someLibraryDesugaringRequired(),
+ b ->
+ b.enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
.compile()
.writeToZip();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
index 8de40b0..697253b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -49,10 +50,10 @@
.addInnerClasses(MinimalInterfaceSuperTest.class)
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
.assertNoMessages()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
index 2b71ee8..df67d51 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
@@ -62,9 +63,9 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addInnerClasses(MonthTest.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(getExpectedResult(parameters));
}
@@ -77,9 +78,9 @@
.addInnerClasses(MonthTest.class)
.addKeepMainRule(MonthTest.Main.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(getExpectedResult(parameters));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
index e2a5d8d..4bee0d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -94,10 +95,10 @@
testForD8()
.addInnerClasses(NeverMergeCoreLibDesugarClasses.class)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .setMinApi(parameters.getRuntime())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addRunClasspathFiles(buildDesugaredLibrary(parameters.getApiLevel()))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello, world!");
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
index 37f834a..c562357 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
@@ -74,10 +75,9 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
.addInnerClasses(NoDefaultMethodOverrideInLibraryTest.class)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(EXPECTED);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java
index 738d9d4..28387d4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java
@@ -6,6 +6,7 @@
import static junit.framework.TestCase.assertTrue;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -54,10 +55,10 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addInnerClasses(SpliteratorTest.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
.inspect(this::validateInterfaces)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(
isJDK11DesugaredLibrary() ? EXPECTED_OUTPUT_JDK11 : EXPECTED_OUTPUT_JDK8);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
index f9d1828..131295c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
@@ -68,7 +68,9 @@
.inspectDiagnosticMessages(this::assertDiagnosis)
.addRunClasspathFiles(customLib)
.run(parameters.getRuntime(), Executor.class)
- .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+ .assertFailureWithErrorThatMatches(
+ containsString(
+ "conversion was impossible because of the non convertible type java.time.Year"));
}
private void assertDiagnosis(TestDiagnosticMessages d) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
index e2b5c8b..7341aae 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -41,9 +42,9 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
.addProgramFiles(extra, convClass)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), APIConversionClass.class)
.assertSuccessWithOutput(GMT);
}
@@ -53,7 +54,8 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
.addProgramClasses(cls)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
.writeToZip();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java
index 3976a4f..043e0ff 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -79,9 +80,9 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramClasses(Executor.class, MyIntUnaryOperator.class)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Executor.class)
.assertSuccessWithOutput(getExpectedResult());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
index 160726b..86bd652 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
@@ -6,6 +6,7 @@
import static junit.framework.TestCase.assertEquals;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.DexRuntime;
import com.android.tools.r8.ToolHelper;
@@ -59,7 +60,11 @@
.setMinApi(AndroidApiLevel.B)
.addProgramClasses(Executor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.builder()
+ .setMinApi(AndroidApiLevel.B)
+ .dontAddRunClasspath()
+ .build())
.compile()
.addDesugaredCoreLibraryRunClassPath(
(AndroidApiLevel api) -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
index 958e8b4..657115c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
@@ -8,10 +8,13 @@
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
import java.util.List;
import java.util.Random;
+import java.util.function.Consumer;
import java.util.stream.IntStream;
import org.junit.Assume;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -25,6 +28,8 @@
private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static Path CUSTOM_LIB;
+
@Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
public static List<Object[]> data() {
return buildParameters(
@@ -36,6 +41,16 @@
this.parameters = parameters;
}
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
+ .compile()
+ .writeToZip();
+ }
+
@Test
public void testAPIConversionNoDesugaring() throws Exception {
Assume.assumeTrue("No need to test twice", shrinkDesugaredLibrary);
@@ -52,7 +67,7 @@
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .addInnerClasses(SuperAPIConversionTest.class)
+ .addProgramClasses(Executor.class, ParallelRandom.class)
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
@@ -71,7 +86,7 @@
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForR8(parameters.getBackend())
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .addInnerClasses(SuperAPIConversionTest.class)
+ .addProgramClasses(Executor.class, ParallelRandom.class)
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Executor.class)
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
@@ -85,6 +100,49 @@
.assertSuccessWithOutputLines("IntStream$VivifiedWrapper");
}
+ @Test
+ public void testAPIConversionDesugaringD8B192351030() throws Exception {
+ Assume.assumeFalse("TODO(b/189435770): fix", shrinkDesugaredLibrary);
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addLibraryClasses(CustomLibClass.class)
+ .addProgramClasses(ExecutorB192351030.class, A.class, B.class, C.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), ExecutorB192351030.class)
+ .assertSuccessWithOutputLines("Hello, ", "world!", "C");
+ }
+
+ @Test
+ public void testAPIConversionDesugaringR8B192351030() throws Exception {
+ Assume.assumeFalse("TODO(b/189435770): fix", shrinkDesugaredLibrary);
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addLibraryClasses(CustomLibClass.class)
+ .addProgramClasses(ExecutorB192351030.class, A.class, B.class, C.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(ExecutorB192351030.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), ExecutorB192351030.class)
+ .assertSuccessWithOutputLines("Hello, ", "world!", "C");
+ }
+
static class ParallelRandom extends Random {
@Override
@@ -100,4 +158,38 @@
System.out.println(intStream.getClass().getSimpleName());
}
}
+
+ static class A extends CustomLibClass {
+ }
+
+ static class B extends A {}
+
+ static class C extends B {
+ void test(Consumer<String> consumer) {
+ super.m(consumer);
+ }
+
+ public void m(Consumer<String> consumer) {
+ consumer.accept("C");
+ }
+ }
+
+ static class ExecutorB192351030 {
+
+ public static void main(String[] args) {
+ new C().test(System.out::println);
+ new C().m(System.out::println);
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+
+ public void m(Consumer<String> consumer) {
+ consumer.accept("Hello, ");
+ consumer.accept("world!");
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
index c4d1f98..c4d2370 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
@@ -65,7 +66,11 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramClasses(TestClass.class)
.addAndroidBuildVersion()
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.builder()
+ .setMinApi(parameters.getApiLevel())
+ .dontAddRunClasspath()
+ .build())
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::assertNoWrappers)
@@ -88,7 +93,8 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramClassesAndInnerClasses(clazz)
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.compile()
.inspect(this::assertNoWrappers)
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 537b873..8553034 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.D8TestBuilder;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.R8;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
@@ -130,7 +131,8 @@
d8TestBuilder
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
.compile();
TestDiagnosticMessages diagnosticMessages = compile.getDiagnosticMessages();
@@ -139,7 +141,6 @@
|| diagnosticMessages.getWarnings().stream()
.noneMatch(x -> x.getDiagnosticMessage().contains("andThen")));
return compile
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.withArt6Plus64BitsLib()
.withArtFrameworks();
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumWithCheckNotNullUserUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithCheckNotNullUserUnboxingTest.java
new file mode 100644
index 0000000..3a28057
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithCheckNotNullUserUnboxingTest.java
@@ -0,0 +1,75 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+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 EnumWithCheckNotNullUserUnboxingTest extends EnumUnboxingTestBase {
+
+ @Parameter(0)
+ public EnumKeepRules enumKeepRules;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, keep: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getAllEnumKeepRules(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "Passed null check (parameter args)",
+ "Passed null check (variable e)",
+ "Passed null check (variable e)",
+ "Passed null check (variable e)");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ Checker.checkNotNull(args, "parameter args");
+ for (MyEnum e : MyEnum.values()) {
+ Checker.checkNotNull(e, "variable e");
+ }
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Checker {
+
+ @NeverInline
+ static <T> T checkNotNull(T t, String msg) {
+ if (t != null) {
+ System.out.println("Passed null check (" + msg + ")");
+ return t;
+ }
+ System.out.println("Expected non-null, got null (" + msg + ")");
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
index 75306f7..dd835a2 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.google.common.base.Predicates.alwaysFalse;
+import static com.google.common.base.Predicates.alwaysTrue;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -179,7 +180,8 @@
factory,
dexType -> dexType.toDescriptorString().equals("Lj$/util/Spliterator$OfPrimitive;"),
Function.identity(),
- null);
+ null,
+ alwaysTrue());
MethodTypeSignature rewritten = rewriter.rewrite(parsedMethodSignature);
assertNotNull(rewritten);
assertTrue(rewritten.hasSignature());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
index c17ea6b..aafeadf 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
@@ -122,7 +122,8 @@
"A", "Lfoo/bar/Baz;", Origin.unknown(), factory, new Reporter());
assertTrue(parsed.hasSignature());
GenericSignatureTypeRewriter rewriter =
- new GenericSignatureTypeRewriter(factory, alwaysTrue(), Function.identity(), null);
+ new GenericSignatureTypeRewriter(
+ factory, alwaysTrue(), Function.identity(), null, alwaysTrue());
FieldTypeSignature rewrittenType = rewriter.rewrite(parsed);
assertNotNull(rewrittenType);
assertTrue(rewrittenType.hasNoSignature());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
index b091f5e..fe4a189 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
@@ -111,7 +111,11 @@
assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
} else {
assertEquals(
- "(" + descriptor(Supplier.class) + descriptor(Predicate.class) + ")TI;",
+ "(L"
+ + binaryName(Supplier.class)
+ + "<*>;L"
+ + binaryName(Predicate.class)
+ + "<Ljava/lang/Object;>;)TI;",
testMethod.getFinalSignatureAttribute());
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
index 177ea63..f412cdf 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.genericsignature.testclasses.Foo;
@@ -29,68 +30,73 @@
@RunWith(Parameterized.class)
public class GenericSignatureKeepReferencesPruneTest extends TestBase {
- private final String[] EXPECTED =
- new String[] {
- Foo.class.getTypeName() + "<java.lang.String>",
- I.class.getTypeName()
- + "<java.lang.Integer, "
- + Foo.class.getTypeName()
- + "<java.lang.Integer>>",
- I.class.getTypeName()
- + "<java.lang.String, "
- + Foo.class.getTypeName()
- + "<java.lang.String>>",
- "Hello world"
- };
-
- private final String[] EXPECTED_FULL_MODE =
- new String[] {
- "class " + Foo.class.getTypeName(),
- "interface " + I.class.getTypeName(),
- "interface " + I.class.getTypeName(),
- "Hello world"
- };
-
private final TestParameters parameters;
private final boolean isCompat;
+ private final boolean minify;
- @Parameters(name = "{0}, isCompat: {1}")
+ @Parameters(name = "{0}, isCompat: {1}, minify: {2}")
public static List<Object[]> data() {
return buildParameters(
- getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
}
- public GenericSignatureKeepReferencesPruneTest(TestParameters parameters, boolean isCompat) {
+ public GenericSignatureKeepReferencesPruneTest(
+ TestParameters parameters, boolean isCompat, boolean minify) {
this.parameters = parameters;
this.isCompat = isCompat;
+ this.minify = minify;
}
@Test
public void testJvm() throws Exception {
assumeTrue(parameters.isCfRuntime());
+ assumeTrue(isCompat);
+ assumeTrue(!minify);
testForJvm()
.addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
.addProgramClassesAndInnerClasses(Main.class)
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines(EXPECTED);
+ .assertSuccessWithOutputLines(getExpected(Foo.class.getTypeName(), I.class.getTypeName()));
}
@Test
public void testR8() throws Exception {
- (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
- .addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
- .addProgramClassesAndInnerClasses(Main.class)
- .addKeepClassAndMembersRules(Main.class)
- .setMinApi(parameters.getApiLevel())
- .addKeepAttributeSignature()
- .addKeepAttributeInnerClassesAndEnclosingMethod()
- .enableInliningAnnotations()
- .enableNeverClassInliningAnnotations()
- .noMinification()
- .compile()
- .inspect(this::inspectSignatures)
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines(isCompat ? EXPECTED : EXPECTED_FULL_MODE);
+ R8TestRunResult runResult =
+ (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+ .addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
+ .addProgramClassesAndInnerClasses(Main.class)
+ .addKeepClassAndMembersRules(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAttributeSignature()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .compile()
+ .inspect(this::inspectSignatures)
+ .run(parameters.getRuntime(), Main.class);
+ runResult.assertSuccess();
+ CodeInspector inspector = runResult.inspector();
+ // Foo and I exists due to the assertions in the inspection.
+ runResult.assertSuccessWithOutputLines(
+ getExpected(
+ inspector.clazz(Foo.class).getFinalName(), inspector.clazz(I.class).getFinalName()));
+ }
+
+ private String[] getExpected(String fooName, String iName) {
+ if (isCompat) {
+ return new String[] {
+ fooName + "<java.lang.String>",
+ iName + "<java.lang.Integer, " + fooName + "<java.lang.Integer>>",
+ iName + "<java.lang.String, " + fooName + "<java.lang.String>>",
+ "Hello world"
+ };
+ } else {
+ return new String[] {
+ "class " + fooName, "interface " + iName, "interface " + iName, "Hello world"
+ };
+ }
}
private void inspectSignatures(CodeInspector inspector) {
@@ -104,7 +110,7 @@
assertEquals(
isCompat
? "<T::Ljava/lang/Comparable<TT;>;R:L"
- + binaryName(Foo.class)
+ + fooClass.getFinalBinaryName()
+ "<TT;>;>Ljava/lang/Object;"
: null,
iClass.getFinalSignatureAttribute());
@@ -113,9 +119,9 @@
assertEquals(
isCompat
? "Ljava/lang/Object;L"
- + binaryName(I.class)
+ + iClass.getFinalBinaryName()
+ "<Ljava/lang/String;L"
- + binaryName(Foo.class)
+ + fooClass.getFinalBinaryName()
+ "<Ljava/lang/String;>;>;"
: null,
fooInnerClass.getFinalSignatureAttribute());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
index 1fb82ab..a040b37 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -13,19 +13,23 @@
import com.android.tools.r8.TestDiagnosticMessagesImpl;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BiPredicateUtils;
+import com.android.tools.r8.utils.MapUtils;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiPredicate;
@@ -113,7 +117,15 @@
GenericSignaturePartialTypeArgumentApplier.build(
appView,
TypeParameterContext.empty()
- .addPrunedSubstitutions(substitutions)
+ .addPrunedSubstitutions(
+ MapUtils.transform(
+ substitutions,
+ HashMap::new,
+ s -> s,
+ ClassTypeSignature::new,
+ (val1, val2) -> {
+ throw new Unreachable("No keys should be merged");
+ }))
.addLiveParameters(liveVariables),
removedLink,
hasFormalTypeParameters);
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterRecursiveTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterRecursiveTest.java
new file mode 100644
index 0000000..79534a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterRecursiveTest.java
@@ -0,0 +1,94 @@
+// 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.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+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 GenericSignaturePrunedOuterRecursiveTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean isCompat;
+
+ @Parameters(name = "{0}, isCompat: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public GenericSignaturePrunedOuterRecursiveTest(TestParameters parameters, boolean isCompat) {
+ this.parameters = parameters;
+ this.isCompat = isCompat;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+ .addInnerClasses(getClass())
+ .addKeepClassAndMembersRules(Foo.class)
+ .addKeepMainRule(Main.class)
+ .addKeepAttributeSignature()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.horizontalClassMergerOptions().disable())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Bar::enclosingMethod")
+ .inspect(this::checkSignatures);
+ }
+
+ public void checkSignatures(CodeInspector inspector) {
+ checkSignature(
+ inspector.clazz(Bar.class.getTypeName() + "$1"),
+ "L" + binaryName(Foo.class) + "<Ljava/util/List<Ljava/lang/Object;>;>;");
+ }
+
+ private void checkSignature(ClassSubject classSubject, String expectedSignature) {
+ assertThat(classSubject, isPresent());
+ assertEquals(isCompat ? expectedSignature : null, classSubject.getFinalSignatureAttribute());
+ }
+
+ public abstract static class Foo<T> {
+
+ void foo(T r) {
+ System.out.println("Hello World");
+ }
+ }
+
+ public static class Bar {
+
+ public static <T extends List<T>> Foo<T> enclosingMethod() {
+ return new Foo<T>() {
+ @Override
+ void foo(T r) {
+ System.out.println("Bar::enclosingMethod");
+ }
+ };
+ }
+
+ public static void run() {
+ enclosingMethod().foo(null);
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Bar.run();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java
index b8d02f6..a1cce6b 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java
@@ -4,15 +4,17 @@
package com.android.tools.r8.graph.genericsignature;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.lang.reflect.Type;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,26 +35,32 @@
this.parameters = parameters;
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testR8() throws Exception {
- testForR8Compat(parameters.getBackend())
- .addInnerClasses(getClass())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .addKeepClassRules(I.class, J.class)
- .addKeepClassAndMembersRulesWithAllowObfuscation(Base.class)
- .addKeepAttributeInnerClassesAndEnclosingMethod()
- .addKeepAttributeSignature()
- .enableInliningAnnotations()
- .enableNeverClassInliningAnnotations()
- .enableNoVerticalClassMergingAnnotations()
- .addVerticallyMergedClassesInspector(
- inspector -> inspector.assertMergedIntoSubtype(A.class))
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertErrorMessageThatMatches(
- containsString("Super type inconsistency in generic signature"));
- });
+ R8TestRunResult runResult =
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(I.class, J.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(Base.class)
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .addKeepAttributeSignature()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .run(parameters.getRuntime(), Main.class);
+ ClassSubject baseSubject = runResult.inspector().clazz(Base.class);
+ assertThat(baseSubject, isPresent());
+ runResult.assertSuccessWithOutputLines(
+ baseSubject.getFinalName() + "<" + String.class.getTypeName() + ">",
+ J.class.getTypeName() + "<X>",
+ // TODO(b/191871201): This could be `X` if we tracked where type-variables are introduced.
+ I.class.getTypeName() + "<java.lang.Object>",
+ "I::t",
+ "B::r");
}
public interface I<T> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 95c8527..fd13bbf 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -9,7 +9,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
@@ -17,13 +16,14 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -39,7 +39,6 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,6 +49,7 @@
public class R8InliningTest extends TestBase {
private static final String DEFAULT_DEX_FILENAME = "classes.dex";
+ private static final String DEFAULT_JAR_FILENAME = "out.jar";
private static final String DEFAULT_MAP_FILENAME = "proguard.map";
private static final String NAME = "inlining";
private static final String KEEP_RULES_FILE = ToolHelper.EXAMPLES_DIR + NAME + "/keep-rules.txt";
@@ -78,18 +78,17 @@
return outputDir.resolve(DEFAULT_DEX_FILENAME);
}
- private List<Path> getGeneratedFiles(Path dir) throws IOException {
+ private Path getGeneratedFile(Path dir) {
if (parameters.isDexRuntime()) {
- return Collections.singletonList(dir.resolve(Paths.get(DEFAULT_DEX_FILENAME)));
+ return dir.resolve(DEFAULT_DEX_FILENAME);
+ } else {
+ assert parameters.isCfRuntime();
+ return dir.resolve(DEFAULT_JAR_FILENAME);
}
- assert parameters.isCfRuntime();
- return Files.walk(dir)
- .filter(f -> f.toString().endsWith(".class"))
- .collect(Collectors.toList());
}
- private List<Path> getGeneratedFiles() throws IOException {
- return getGeneratedFiles(outputDir);
+ private Path getGeneratedFile() {
+ return getGeneratedFile(outputDir);
}
private String getGeneratedProguardMap() {
@@ -110,38 +109,45 @@
private void generateR8Version(Path out, Path mapFile, boolean inlining) throws Exception {
assert parameters.isDexRuntime() || parameters.isCfRuntime();
- R8Command.Builder commandBuilder =
- R8Command.builder()
- .addProgramFiles(getInputFile())
- .setOutput(out, outputMode(parameters.getBackend()))
- .addProguardConfigurationFiles(Paths.get(KEEP_RULES_FILE))
- .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
- .setDisableMinification(true);
- if (mapFile != null) {
- commandBuilder.setProguardMapOutputPath(mapFile);
- }
- if (parameters.isDexRuntime()) {
- commandBuilder.setMinApiLevel(parameters.getApiLevel().getLevel());
- }
- if (allowAccessModification) {
- commandBuilder.addProguardConfiguration(
- ImmutableList.of("-allowaccessmodification"), Origin.unknown());
- }
- ToolHelper.allowTestProguardOptions(commandBuilder);
- ToolHelper.runR8(
- commandBuilder.build(),
- o -> {
- // Disable class inlining to prevent that the instantiation of Nullability is removed, and
- // that the class is therefore made abstract.
- o.enableClassInlining = false;
- o.enableInlining = inlining;
- o.enableInliningOfInvokesWithNullableReceivers = false;
- o.inliningInstructionLimit = 6;
- // Tests depend on nullability of receiver and argument in general. Learning very accurate
- // nullability from actual usage in tests bothers what we want to test.
- o.callSiteOptimizationOptions().disableTypePropagationForTesting();
- o.testing.horizontallyMergedClassesConsumer = this::fixInliningNullabilityClass;
- });
+ testForR8(parameters.getBackend())
+ .addProgramFiles(getInputFile())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addKeepRuleFiles(Paths.get(KEEP_RULES_FILE))
+ .addOptionsModification(
+ o -> {
+ // Disable class inlining to prevent that the instantiation of Nullability is removed,
+ // and
+ // that the class is therefore made abstract.
+ o.enableClassInlining = false;
+ o.enableInlining = inlining;
+ o.enableInliningOfInvokesWithNullableReceivers = false;
+ o.inliningInstructionLimit = 6;
+ // Tests depend on nullability of receiver and argument in general. Learning very
+ // accurate
+ // nullability from actual usage in tests bothers what we want to test.
+ o.callSiteOptimizationOptions().disableTypePropagationForTesting();
+ o.testing.horizontallyMergedClassesConsumer = this::fixInliningNullabilityClass;
+ o.testing.horizontalClassMergingTarget =
+ (appView, candidates, target) -> {
+ for (DexProgramClass candidate : candidates) {
+ if (SyntheticItemsTestUtils.isEnumUnboxingSharedUtilityClass(
+ candidate.getClassReference())) {
+ return candidate;
+ }
+ }
+ return target;
+ };
+ })
+ .allowAccessModification(allowAccessModification)
+ .enableProguardTestOptions()
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .applyIf(
+ parameters.isCfRuntime(),
+ result -> result.writeToZip(out.resolve(DEFAULT_JAR_FILENAME)),
+ result -> result.writeSingleDexOutputToFile(out.resolve(DEFAULT_DEX_FILENAME)))
+ .applyIf(mapFile != null, result -> result.writeProguardMap(mapFile));
}
@Before
@@ -163,7 +169,7 @@
ToolHelper.runJava(
parameters.getRuntime().asCf(),
Collections.singletonList("-noverify"),
- Collections.singletonList(outputDir),
+ Collections.singletonList(outputDir.resolve(DEFAULT_JAR_FILENAME)),
"inlining.Inlining")
.stdout;
}
@@ -199,7 +205,7 @@
@Test
public void checkNoInvokes() throws Throwable {
CodeInspector inspector =
- new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
+ new CodeInspector(ImmutableList.of(getGeneratedFile()), getGeneratedProguardMap(), null);
ClassSubject clazz = inspector.clazz("inlining.Inlining");
// Simple constant inlining.
@@ -222,13 +228,9 @@
}
private long sumOfClassFileSizes(Path dir) throws IOException {
- long size = 0;
- for (Path p : getGeneratedFiles(dir)) {
- if (ZipUtils.isClassFile(p.toString())) {
- size += p.toFile().length();
- }
- }
- return size;
+ IntBox size = new IntBox();
+ ZipUtils.iter(getGeneratedFile(dir), (a, b) -> size.increment((int) a.getSize()));
+ return size.get();
}
@Test
@@ -241,11 +243,6 @@
Path nonInlinedDexFile = nonInlinedOutputDir.resolve(DEFAULT_DEX_FILENAME);
nonInlinedSize = Files.size(nonInlinedDexFile);
inlinedSize = Files.size(getGeneratedDexFile());
- final boolean ALWAYS_DUMP = false; // Used for debugging.
- if (ALWAYS_DUMP || inlinedSize > nonInlinedSize) {
- dump(nonInlinedDexFile, "No inlining");
- dump(getGeneratedDexFile(), "Inlining enabled");
- }
} else {
assert parameters.isCfRuntime();
nonInlinedSize = sumOfClassFileSizes(nonInlinedOutputDir);
@@ -316,7 +313,7 @@
@Test
public void invokeOnNullableReceiver() throws Exception {
CodeInspector inspector =
- new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
+ new CodeInspector(ImmutableList.of(getGeneratedFile()), getGeneratedProguardMap(), null);
// These constants describe the expected number of invoke instructions calling a possibly
// inlined method.
@@ -353,7 +350,7 @@
@Test
public void invokeOnNonNullReceiver() throws Exception {
CodeInspector inspector =
- new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
+ new CodeInspector(ImmutableList.of(getGeneratedFile()), getGeneratedProguardMap(), null);
ClassSubject clazz = inspector.clazz(nullabilityClass);
assertThat(clazz.uniqueMethodWithName("conditionalOperator"), isAbsent());
@@ -369,19 +366,11 @@
InstructionSubject instruction = iterator.next();
if (instruction.isInstanceGet()) {
++instanceGetCount;
- } else if (instruction.isInvoke() && !isEnumInvoke(instruction)) {
+ } else if (instruction.isInvoke()) {
++invokeCount;
}
}
assertEquals(1, instanceGetCount);
assertEquals(0, invokeCount);
}
-
- private boolean isEnumInvoke(InstructionSubject instruction) {
- return instruction
- .getMethod()
- .getName()
- .toString()
- .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX);
- }
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
index 4f60021..1f7b39a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
@@ -57,9 +57,8 @@
assertThat(main, isPresent());
MethodSubject mainMethod = main.uniqueMethodWithName("main");
assertThat(mainMethod, isPresent());
- // TODO(b/160856783): Investigate if this can be removed.
assertEquals(
- 1,
+ 0,
mainMethod
.streamInstructions()
.filter(instruction -> instruction.isCheckCast(Main.class.getTypeName()))
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
index 70c10da..03b1280 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -39,7 +40,8 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramClasses(TestClass.class)
.addKeepMainRule(TestClass.class)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineInstructionWithRepeatedOperandTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineInstructionWithRepeatedOperandTest.java
new file mode 100644
index 0000000..f7208bd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineInstructionWithRepeatedOperandTest.java
@@ -0,0 +1,65 @@
+// 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.optimize.outliner;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/192023718. */
+@RunWith(Parameterized.class)
+public class OutlineInstructionWithRepeatedOperandTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> {
+ options.outline.minSize = 2;
+ options.outline.threshold = 2;
+ })
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0", "0");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ int zero = System.currentTimeMillis() > 0 ? 0 : -1;
+ System.out.println(m1(zero));
+ System.out.println(m2(zero));
+ }
+
+ @NeverInline
+ static int m1(int x) {
+ int y = x * 42;
+ return y * y * y;
+ }
+
+ @NeverInline
+ static int m2(int x) {
+ int y = x * 42;
+ return y * y * y;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index ca5b2c0..07767cd 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.smali.SmaliBuilder;
@@ -62,18 +63,21 @@
MyRegisterAllocator allocator = new MyRegisterAllocator(appView, code);
// Setup live an inactive live interval with ranges [0, 10[ and [20, 30[ with only
// uses in the first interval and which is linked to another interval.
- LiveIntervals inactiveIntervals = new LiveIntervals(new Value(0, TypeElement.getInt(), null));
+ LiveIntervals inactiveIntervals =
+ new LiveIntervals(ensureDefinition(new Value(0, TypeElement.getInt(), null), 0));
inactiveIntervals.addRange(new LiveRange(0, 10));
inactiveIntervals.addUse(new LiveIntervalsUse(0, 10));
inactiveIntervals.addUse(new LiveIntervalsUse(4, 10));
inactiveIntervals.addRange(new LiveRange(20, 30));
inactiveIntervals.setRegister(0);
- LiveIntervals linked = new LiveIntervals(new Value(1, TypeElement.getInt(), null));
+ LiveIntervals linked =
+ new LiveIntervals(ensureDefinition(new Value(1, TypeElement.getInt(), null), 1));
linked.setRegister(1);
inactiveIntervals.link(linked);
allocator.addInactiveIntervals(inactiveIntervals);
// Setup an unhandled interval that overlaps the inactive interval.
- LiveIntervals unhandledIntervals = new LiveIntervals(new Value(2, TypeElement.getInt(), null));
+ LiveIntervals unhandledIntervals =
+ new LiveIntervals(ensureDefinition(new Value(2, TypeElement.getInt(), null), 2));
unhandledIntervals.addRange(new LiveRange(12, 24));
// Split the overlapping inactive intervals and check that after the split, the second
// part of the inactive interval is unhandled and will therefore get a new register
@@ -83,4 +87,10 @@
assert allocator.getUnhandled().peek().getStart() == 20;
assert allocator.getUnhandled().peek().getEnd() == 30;
}
+
+ private Value ensureDefinition(Value value, int index) {
+ Argument argument = new Argument(value, index, false);
+ value.definition = argument;
+ return value;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index c8af414..e418ced 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -5,7 +5,7 @@
package com.android.tools.r8.kotlin;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -62,7 +62,7 @@
@Test
public void testJStyleLambdas() throws Exception {
// TODO(b/185497606): Unable to class inline j style lambdas.
- assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
+ assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
String mainClassName = "class_inliner_lambda_j_style.MainKt";
runTest(
"class_inliner_lambda_j_style",
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 2b2eed4..3d872c4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -278,7 +278,7 @@
assertTrue(fieldSubject.getField().accessFlags.isPublic());
// kotlinc 1.5 do not generate accessors for public late-init properties.
- if (kotlinc.isNot(KOTLINC_1_5_0_M2)) {
+ if (kotlinc.isNot(KOTLINC_1_5_0)) {
checkMethodIsRemoved(outerClass, getterAccessor);
checkMethodIsRemoved(outerClass, setterAccessor);
}
@@ -311,7 +311,7 @@
assertTrue(fieldSubject.getField().accessFlags.isPublic());
// kotlinc 1.5 do not generate accessors for public late-init properties.
- if (kotlinc.isNot(KOTLINC_1_5_0_M2)) {
+ if (kotlinc.isNot(KOTLINC_1_5_0)) {
checkMethodIsRemoved(outerClass, getterAccessor);
checkMethodIsRemoved(outerClass, setterAccessor);
}
@@ -376,7 +376,7 @@
@Test
public void testAccessorForInnerClassIsRemovedWhenNotUsed() throws Exception {
// TODO(b/185493636): Kotlinc 1.5 generated property accessors are not removed.
- assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
+ assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
String mainClass =
addMainToClasspath(
"accessors.PropertyAccessorForInnerClassKt", "noUseOfPropertyAccessorFromInnerClass");
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index ec3722c..aebbbb2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -80,7 +80,7 @@
inspector -> {
if (allowAccessModification
&& kotlinParameters.is(
- KotlinCompilerVersion.KOTLINC_1_5_0_M2, KotlinTargetVersion.JAVA_8)) {
+ KotlinCompilerVersion.KOTLINC_1_5_0, KotlinTargetVersion.JAVA_8)) {
checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
} else {
ClassSubject dataClass =
@@ -131,7 +131,7 @@
inspector -> {
if (allowAccessModification
&& kotlinParameters.is(
- KotlinCompilerVersion.KOTLINC_1_5_0_M2, KotlinTargetVersion.JAVA_8)) {
+ KotlinCompilerVersion.KOTLINC_1_5_0, KotlinTargetVersion.JAVA_8)) {
checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
} else {
ClassSubject dataClass =
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index 00d2418..7b02bdb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.kotlin.lambda;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
import static com.android.tools.r8.utils.PredicateUtils.not;
import static junit.framework.TestCase.assertEquals;
@@ -75,7 +75,7 @@
@Test
public void testR8() throws Exception {
// TODO(b/185497606): Unable to merge jstyle lambda.
- assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
+ assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
testForR8(parameters.getBackend())
.addProgramFiles(getProgramFiles())
.addKeepMainRule(getMainClassName())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index deb4e5e..6bb1804 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin.metadata;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
@@ -140,7 +140,7 @@
KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
assertThat(age, isPresent());
assertThat(age, not(isExtensionProperty()));
- assertEquals(kotlinc.is(KOTLINC_1_5_0_M2) ? "b:I" : "a:I", age.fieldSignature().asString());
+ assertEquals(kotlinc.is(KOTLINC_1_5_0) ? "b:I" : "a:I", age.fieldSignature().asString());
assertEquals("getAge()I", age.getterSignature().asString());
assertEquals("setAge(I)V", age.setterSignature().asString());
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 4f5f64f..d3380e6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -164,7 +164,7 @@
.compileRaw();
assertNotEquals(0, kotlinTestCompileResult.exitCode);
- if (kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+ if (kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0)) {
assertThat(
kotlinTestCompileResult.stderr,
containsString(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 44092e7..263b087 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -50,8 +50,8 @@
return 597;
} else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
return 685;
- } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
- return 694;
+ } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0)) {
+ return 696;
} else {
throw new Unreachable("Should not compile in this configuration");
}
@@ -62,8 +62,8 @@
return 327;
} else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
return 413;
- } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
- return 417;
+ } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0)) {
+ return 419;
} else {
throw new Unreachable("Should not compile in this configuration");
}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index e25fd15..80d865e 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -74,6 +74,6 @@
// TODO(b/179994975): Kotlin enum changed in 1.5.
assertThat(
enumClass.clinit(),
- kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0_M2) ? isPresent() : isAbsent());
+ kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0) ? isPresent() : isAbsent());
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index bd10054..a3ae2e9 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -47,9 +46,7 @@
}
private void checkSignature(CodeInspector inspector, String signature) {
- String fooImplFinalDescriptor =
- DescriptorUtils.javaTypeToDescriptor(inspector.clazz(FooImpl.class).getFinalName());
- assertEquals("()" + fooImplFinalDescriptor, signature);
+ assertEquals("()" + inspector.clazz(FooImpl.class).getFinalDescriptor(), signature);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
index dc7458c..e5ed600 100644
--- a/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
+++ b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
@@ -42,7 +42,7 @@
getTestParameters().withDexRuntimes().withAllApiLevels().build(),
getKotlinTestParameters()
.withTargetVersion(KotlinTargetVersion.JAVA_8)
- .withCompiler(ToolHelper.getKotlinC_1_5_0_m2())
+ .withCompiler(ToolHelper.getKotlinC_1_5_0())
.build());
}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 926b2c0..d98725d 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -197,14 +197,12 @@
inspector.clazz(CLASS_WITH_ANNOTATED_METHOD).allMethods();
assertEquals(
1, methods.stream().filter(m -> m.getOriginalName().equals("<init>")).count());
- // TODO(b/132318609): This should have the annotated method, but does not due to the
- // annotation being removed.
assertEquals(
- 0,
+ 1,
methods.stream()
.filter(m -> m.getOriginalName().equals(ANNOTATED_METHOD))
.count());
- assertEquals(1, methods.size());
+ assertEquals(2, methods.size());
});
}
@@ -216,8 +214,6 @@
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
- // TODO(b/132318609): Remove keep annotation once fixed.
- .addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
@@ -231,8 +227,6 @@
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
- // TODO(b/132318609): Remove keep annotation once fixed.
- .addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules(
"-if class * "
+ "-keepclassmembers class <1> { @"
@@ -251,8 +245,6 @@
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
- // TODO(b/132318609): Remove keep annotation once fixed.
- .addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules(
"-if class * "
+ "-keepclasseswithmembers class <1> { @"
@@ -271,8 +263,6 @@
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
- // TODO(b/132318609): Remove keep annotation once fixed.
- .addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules(
"-if class * { @"
+ PRESENT_ANNOTATION
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
index f198845..e6e8b69 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
@@ -64,8 +64,6 @@
if (shrinker.isR8()) {
run(
testForR8(parameters.getBackend())
- // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
- .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
.enableNoHorizontalClassMergingAnnotations());
} else {
run(
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
index 8b01419..314ea33 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
@@ -63,10 +63,7 @@
@Test
public void test() throws Exception {
if (shrinker.isR8()) {
- run(
- testForR8(parameters.getBackend())
- // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
- .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+ run(testForR8(parameters.getBackend()));
} else {
run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
index 5a1d0f9..5305ced 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -64,8 +64,6 @@
if (shrinker.isR8()) {
run(
testForR8(parameters.getBackend())
- // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
- .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
.enableNoHorizontalClassMergingAnnotations());
} else {
run(
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepAllowAnnotationRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepAllowAnnotationRemovalTest.java
new file mode 100644
index 0000000..a47afde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepAllowAnnotationRemovalTest.java
@@ -0,0 +1,71 @@
+// 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 KeepAllowAnnotationRemovalTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableCompatibilityMode;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, compat: {0}")
+ public static List<Object[]> parameters() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8Compat(parameters.getBackend(), enableCompatibilityMode)
+ .addProgramClasses(Main.class, Keep.class)
+ .addKeepRules(
+ "-keep,allowannotationremoval @" + Keep.class.getTypeName() + " class * {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .addKeepRuntimeInvisibleAnnotations()
+ .enableProguardTestOptions()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(Main.class);
+ assertThat(classSubject, isPresent());
+ assertThat(
+ classSubject.annotation(Keep.class),
+ onlyIf(enableCompatibilityMode, isPresent()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .apply(
+ result ->
+ result.assertSuccessWithOutputLines(
+ result.inspector().clazz(Keep.class).getFinalName()));
+ }
+
+ @Keep
+ static class Main {
+ public static void main(String[] args) {
+ System.out.println(Keep.class.getName());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java
new file mode 100644
index 0000000..8fa7bc1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java
@@ -0,0 +1,97 @@
+// 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsThrow;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 KeepDisallowAnnotationRemovalAllowOptimizationTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableCompatibilityMode;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, compat: {0}")
+ public static List<Object[]> parameters() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8Compat(parameters.getBackend(), enableCompatibilityMode)
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(NeverInline.class)
+ .addKeepRules(
+ "-keepclassmembers,allowobfuscation,allowoptimization,allowshrinking class * {",
+ " static java.lang.Object getNonNull();",
+ "}")
+ .addKeepRuntimeInvisibleAnnotations()
+ // In compatibility mode the rule above is a no-op.
+ .allowUnusedProguardConfigurationRules(enableCompatibilityMode)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(Main.class);
+ assertThat(classSubject, isPresent());
+
+ // The annotation on getNonNull() is kept meanwhile it is subject to other
+ // optimizations.
+ MethodSubject getNonNullSubject = classSubject.uniqueMethodWithName("getNonNull");
+ assertThat(getNonNullSubject, isPresentAndRenamed());
+ assertThat(getNonNullSubject.annotation(NeverInline.class), isPresent());
+
+ // Check that the code has been optimized using the fact that getNonNull() returns a
+ // non-null value.
+ assertThat(classSubject.uniqueMethodWithName("dead"), isAbsent());
+ assertThat(classSubject.mainMethod(), not(containsThrow()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("getNonNull()");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ Object o = getNonNull();
+ if (o == null) {
+ dead();
+ }
+ }
+
+ @NeverInline
+ static Object getNonNull() {
+ System.out.println("getNonNull()");
+ return new Object();
+ }
+
+ @NeverInline
+ static void dead() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java
index 6913147..de259e5 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java
@@ -66,7 +66,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public SerializedNameAlternateTest(TestParameters parameters) {
@@ -83,7 +83,14 @@
.addKeepMainRule(Main.class)
.addKeepClassRules(Foo.class)
.addKeepClassRules(SerializedName.class)
- .setMinApi(parameters.getRuntime())
+ .addKeepRules(
+ // Non-compat mode only retains annotations for items matched by a -keep rule.
+ "-keepclassmembers,allowobfuscation,allowshrinking class "
+ + Foo.class.getTypeName()
+ + " {",
+ " @" + SerializedName.class.getTypeName() + " <fields>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
.noMinification()
.run(parameters.getRuntime(), Main.class);
checkRunResult(result);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
index edbcd98..dfe70d5 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -54,11 +54,7 @@
CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- if (shrinker.isFullModeR8()) {
- verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
- } else {
- verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
- }
+ verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
}
@Test
@@ -83,11 +79,7 @@
CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- if (shrinker.isFullModeR8()) {
- verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
- } else {
- verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
- }
+ verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
}
@Test
@@ -110,11 +102,6 @@
CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- if (shrinker.isFullModeR8()) {
- verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
- } else {
- verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
- }
+ verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
}
-
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java
new file mode 100644
index 0000000..a146368
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java
@@ -0,0 +1,72 @@
+// 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.shaking.keepclassmembers;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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 UnsatisfiedDependentNoObfuscationTest 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)
+ .addKeepRules(
+ // TODO(b/192636793): This rule should not have any impact on the compilation, since
+ // GreeterConsumer is dead.
+ "-keepclassmembers,includedescriptorclasses class "
+ + GreeterConsumer.class.getTypeName()
+ + " {",
+ " <methods>;",
+ "}")
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ // TODO(b/192636793): This should be renamed.
+ assertThat(inspector.clazz(Greeter.class), isPresentAndNotRenamed());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ Greeter.hello();
+ }
+ }
+
+ static class Greeter {
+ @NeverInline
+ static void hello() {
+ System.out.println("Hello");
+ }
+ }
+
+ static class GreeterConsumer {
+ void accept(Greeter greeter) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
index d86cc6c..bca691f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
@@ -5,9 +5,16 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.graph.AccessFlags;
+import java.lang.annotation.Annotation;
public abstract class ClassOrMemberSubject extends Subject {
+ public abstract AnnotationSubject annotation(String name);
+
+ public final AnnotationSubject annotation(Class<? extends Annotation> clazz) {
+ return annotation(clazz.getTypeName());
+ }
+
public abstract AccessFlags<?> getAccessFlags();
public abstract String getOriginalName();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 452669a..4f8ecd3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -191,8 +191,6 @@
public abstract DexProgramClass getDexProgramClass();
- public abstract AnnotationSubject annotation(String name);
-
@Override
public abstract String getOriginalName();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 10a68c1..c5a8cde 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -44,6 +44,27 @@
};
}
+ public static Matcher<MethodSubject> containsThrow() {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ protected boolean matchesSafely(MethodSubject subject) {
+ return subject.isPresent()
+ && subject.getMethod().hasCode()
+ && subject.streamInstructions().anyMatch(InstructionSubject::isThrow);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("contains throw");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject subject, Description description) {
+ description.appendText("method did not");
+ }
+ };
+ }
+
public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
return instantiatesClass(clazz.getTypeName());
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index d8fd4b9..bd9081e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -38,8 +38,6 @@
public abstract String getFinalSignatureAttribute();
- public abstract AnnotationSubject annotation(String name);
-
public FieldSubject asFieldSubject() {
return null;
}
diff --git a/third_party/kotlin/kotlin-compiler-1.5.0.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-1.5.0.tar.gz.sha1
new file mode 100644
index 0000000..168df33
--- /dev/null
+++ b/third_party/kotlin/kotlin-compiler-1.5.0.tar.gz.sha1
@@ -0,0 +1 @@
+f7331df1f60104865f8308af89384ff2dee29427
\ No newline at end of file
diff --git a/tools/asmifier.py b/tools/asmifier.py
index 8aeb9a7..3fec231 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -10,7 +10,7 @@
import sys
import utils
-ASM_VERSION = '9.0'
+ASM_VERSION = '9.2'
ASM_JAR = 'asm-' + ASM_VERSION + '.jar'
ASM_UTIL_JAR = 'asm-util-' + ASM_VERSION + '.jar'
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 46b09b8..83bc2aa 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -300,6 +300,7 @@
cmd.append('-Xmx' + args.xmx)
if args.ea:
cmd.append('-ea')
+ cmd.append('-Dcom.android.tools.r8.enableTestAssertions=1')
if args.print_times:
cmd.append('-Dcom.android.tools.r8.printtimes=1')
if hasattr(args, 'properties'):