Version 1.4.50
Cherry-pick: Cache synthesized classes at AppInfo level.
CL: https://r8-review.googlesource.com/c/r8/+/34320
Cherry-pick: Refactor InstructionIterator to use ListIterator
CL: https://r8-review.googlesource.com/c/r8/+/33111
Cherry-pick: Add tests and TODOs for rebinding of fields
CL: https://r8-review.googlesource.com/c/r8/+/33840
Cherry-pick: Revert "Revert "Disallow const instructions after throwing instructions""
CL: https://r8-review.googlesource.com/c/r8/+/33227
Cherry-pick: Don't allow fill-array-data in consistency check.
CL: https://r8-review.googlesource.com/c/r8/+/33109
Cherry-pick: Refactor rewriteWithConstant to make code clearer
CL: https://r8-review.googlesource.com/c/r8/+/33225
Cherry-pick: Ignore <clinit> of field holder in member value propagation
CL: https://r8-review.googlesource.com/c/r8/+/33844
Bug: 123857022, 113374256, 124155517, 124593221, 74379749
Change-Id: Ia74e53a026ff1c70fc9c859675dd87275c50d2c8
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b31bcbd..a6b94b0 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.49";
+ public static final String LABEL = "1.4.50";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 028f5a2..db83f6a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,22 +3,17 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -28,6 +23,10 @@
public final DexItemFactory dexItemFactory;
private final ConcurrentHashMap<DexType, Map<Descriptor<?,?>, KeyedDexItem<?>>> definitions =
new ConcurrentHashMap<>();
+ // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
+ // class being optimized.
+ private ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses =
+ new ConcurrentHashMap<>();
public AppInfo(DexApplication application) {
this.app = application;
@@ -48,6 +47,16 @@
this(application);
}
+ public void addSynthesizedClass(DexProgramClass clazz) {
+ assert clazz.type.isD8R8SynthesizedClassType();
+ DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
+ assert previous == null || previous == clazz;
+ }
+
+ public Collection<DexProgramClass> getSynthesizedClassesForSanityCheck() {
+ return Collections.unmodifiableCollection(synthesizedClasses.values());
+ }
+
private Map<Descriptor<?,?>, KeyedDexItem<?>> computeDefinitions(DexType type) {
Builder<Descriptor<?,?>, KeyedDexItem<?>> builder = ImmutableMap.builder();
DexClass clazz = app.definitionFor(type);
@@ -78,6 +87,11 @@
}
public DexClass definitionFor(DexType type) {
+ DexProgramClass cached = synthesizedClasses.get(type);
+ if (cached != null) {
+ assert app.definitionFor(type) == null;
+ return cached;
+ }
return app.definitionFor(type);
}
@@ -524,51 +538,6 @@
return result;
}
- public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
- DexClass clazz = definitionFor(type);
- assert clazz != null;
- return canTriggerStaticInitializer(clazz, ignoreTypeItself);
- }
-
- public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
- Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
-
- // Process superclass chain.
- DexClass current = clazz;
- while (current != null && current.type != dexItemFactory.objectType) {
- if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
- return true;
- }
- knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
- current = current.superType != null ? definitionFor(current.superType) : null;
- }
-
- // Process interfaces.
- Queue<DexType> queue = new ArrayDeque<>(knownInterfaces);
- while (!queue.isEmpty()) {
- DexType iface = queue.remove();
- DexClass definition = definitionFor(iface);
- if (canTriggerStaticInitializer(definition)) {
- return true;
- }
- if (!definition.isInterface()) {
- throw new Unreachable(iface.toSourceString() + " is expected to be an interface");
- }
-
- for (DexType superIface : definition.interfaces.values) {
- if (knownInterfaces.add(superIface)) {
- queue.add(superIface);
- }
- }
- }
- return false;
- }
-
- public static boolean canTriggerStaticInitializer(DexClass clazz) {
- // Assume it *may* trigger if we didn't find the definition.
- return clazz == null || clazz.hasClassInitializer();
- }
-
public interface ResolutionResult {
DexEncodedMethod asResultOfResolve();
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 828d98d..5e65aa3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -567,7 +567,8 @@
}
public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
- if (ignore.test(type)) {
+ if (ignore.test(type)
+ || appInfo.dexItemFactory.libraryTypesWithoutStaticInitialization.contains(type)) {
return false;
}
if (hasNonTrivialClassInitializer()) {
@@ -576,6 +577,15 @@
if (defaultValuesForStaticFieldsMayTriggerAllocation()) {
return true;
}
+ return initializationOfParentTypesMayHaveSideEffects(appInfo, ignore);
+ }
+
+ public boolean initializationOfParentTypesMayHaveSideEffects(AppInfo appInfo) {
+ return initializationOfParentTypesMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+ }
+
+ public boolean initializationOfParentTypesMayHaveSideEffects(
+ AppInfo appInfo, Predicate<DexType> ignore) {
for (DexType iface : interfaces.values) {
if (iface.classInitializationMayHaveSideEffects(appInfo, ignore)) {
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 42cc0ce..96f34bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
public class DexEncodedField extends KeyedDexItem<DexField> {
@@ -109,22 +110,23 @@
}
// Returns a const instructions if this field is a compile time final const.
- public Instruction valueAsConstInstruction(AppInfo appInfo, Value dest) {
+ public Instruction valueAsConstInstruction(AppInfoWithLiveness appInfo, Value dest) {
// The only way to figure out whether the DexValue contains the final value
// is ensure the value is not the default or check <clinit> is not present.
- if (accessFlags.isStatic() && accessFlags.isPublic() && accessFlags.isFinal()) {
- DexClass clazz = appInfo.definitionFor(field.getHolder());
+ boolean isEffectivelyFinal =
+ (accessFlags.isFinal() || !appInfo.isFieldWritten(field))
+ && !appInfo.isPinned(field);
+ if (!isEffectivelyFinal) {
+ return null;
+ }
+ if (accessFlags.isStatic()) {
+ DexClass clazz = appInfo.definitionFor(field.clazz);
assert clazz != null : "Class for the field must be present";
return getStaticValue().asConstInstruction(clazz.hasClassInitializer(), dest);
}
return null;
}
- public DexEncodedField toRenamedField(DexString name, DexItemFactory dexItemFactory) {
- return new DexEncodedField(dexItemFactory.createField(field.clazz, field.type, name),
- accessFlags, annotations, staticValue);
- }
-
public DexEncodedField toTypeSubstitutedField(DexField field) {
if (this.field == field) {
return this;
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 8bdf115..8cd38d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -298,6 +298,7 @@
public final DexType metafactoryType = createType("Ljava/lang/invoke/LambdaMetafactory;");
public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
+ public final DexType iteratorType = createType("Ljava/util/Iterator;");
public final DexType serializableType = createType("Ljava/io/Serializable;");
public final DexType externalizableType = createType("Ljava/io/Externalizable;");
public final DexType comparableType = createType("Ljava/lang/Comparable;");
@@ -348,6 +349,9 @@
createString("makeConcat")
);
+ public final Set<DexType> libraryTypesWithoutStaticInitialization =
+ ImmutableSet.of(iteratorType, serializableType);
+
private boolean skipNameValidationForTesting = false;
public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 9182811..1a880f6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -10,10 +10,13 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.google.common.collect.ImmutableList;
+import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -121,11 +124,25 @@
return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
}
+ public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+ return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+ }
+
public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
DexClass clazz = appInfo.definitionFor(this);
return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo, ignore);
}
+ public boolean initializationOfParentTypesMayHaveSideEffects(AppInfo appInfo) {
+ return initializationOfParentTypesMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+ }
+
+ public boolean initializationOfParentTypesMayHaveSideEffects(
+ AppInfo appInfo, Predicate<DexType> ignore) {
+ DexClass clazz = appInfo.definitionFor(this);
+ return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appInfo, ignore);
+ }
+
public boolean isUnknown() {
return hierarchyLevel == UNKNOWN_LEVEL;
}
@@ -453,7 +470,9 @@
|| name.contains(DISPATCH_CLASS_NAME_SUFFIX)
|| name.contains(LAMBDA_CLASS_NAME_PREFIX)
|| name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
- || name.contains(OutlineOptions.CLASS_NAME);
+ || name.contains(OutlineOptions.CLASS_NAME)
+ || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
+ || name.contains(Java8MethodRewriter.UTILITY_CLASS_NAME_PREFIX);
}
public int elementSizeForPrimitiveArrayType() {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index bac74f6..402b56d 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -388,9 +388,7 @@
public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
public DexEncodedMethod mapDexEncodedMethod(
- DexEncodedMethod originalEncodedMethod,
- AppInfo appInfo,
- Map<DexType, DexProgramClass> synthesizedClasses) {
+ DexEncodedMethod originalEncodedMethod, AppInfo appInfo) {
DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
// Note that:
// * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
@@ -398,14 +396,7 @@
// * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
// updated either yet.
DexClass newHolder = appInfo.definitionFor(newMethod.holder);
-
- // TODO(b/120130831): Need to ensure that all synthesized classes are part of the application.
- if (newHolder == null) {
- newHolder = synthesizedClasses.get(newMethod.holder);
- }
-
assert newHolder != null;
-
DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
assert newEncodedMethod != null;
return newEncodedMethod;
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 f961b80..649d1af 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
@@ -662,13 +662,9 @@
}
// After the throwing instruction only debug instructions and the final jump
// instruction is allowed.
- // TODO(ager): For now allow const instructions due to the way consts are pushed
- // towards their use
if (seenThrowing) {
assert instruction.isDebugInstruction()
|| instruction.isJumpInstruction()
- || instruction.isConstInstruction()
- || instruction.isNewArrayFilledData()
|| instruction.isStore()
|| instruction.isPop();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
index b98662c..cdb40bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
@@ -19,10 +19,7 @@
@Override
public boolean hasNext() {
- if (instructionIterator.hasNext()) {
- return true;
- }
- return blockIterator.hasNext();
+ return instructionIterator.hasNext() || blockIterator.hasNext();
}
@Override
@@ -39,6 +36,35 @@
}
@Override
+ public boolean hasPrevious() {
+ return instructionIterator.hasPrevious() || blockIterator.hasPrevious();
+ }
+
+ @Override
+ public Instruction previous() {
+ if (instructionIterator.hasPrevious()) {
+ return instructionIterator.previous();
+ }
+ if (!blockIterator.hasPrevious()) {
+ throw new NoSuchElementException();
+ }
+ BasicBlock block = blockIterator.previous();
+ instructionIterator = block.listIterator(block.getInstructions().size());
+ assert instructionIterator.hasPrevious();
+ return instructionIterator.previous();
+ }
+
+ @Override
+ public int nextIndex() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int previousIndex() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void add(Instruction instruction) {
instructionIterator.add(instruction);
}
@@ -49,6 +75,11 @@
}
@Override
+ public void set(Instruction instruction) {
+ instructionIterator.set(instruction);
+ }
+
+ @Override
public void replaceCurrentInstruction(Instruction newInstruction) {
instructionIterator.replaceCurrentInstruction(newInstruction);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
index 2f2b3f1..72aa3ee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.ir.code;
-public interface InstructionIterator extends NextUntilIterator<Instruction> {
+import java.util.ListIterator;
+
+public interface InstructionIterator
+ extends ListIterator<Instruction>, NextUntilIterator<Instruction> {
/**
* Replace the current instruction (aka the {@link Instruction} returned by the previous call to
* {@link #next} with the passed in <code>newInstruction</code>.
@@ -22,15 +25,6 @@
*/
void replaceCurrentInstruction(Instruction newInstruction);
- /**
- * Adds an instruction. The instruction will be added just before the current
- * cursor position.
- *
- * The instruction will be assigned to the block it is added to.
- *
- * @param instruction The instruction to add.
- */
- void add(Instruction instruction);
/**
* Safe removal function that will insert a DebugLocalRead to take over the debug values if any
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index a5e5d78..c232d3f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -11,9 +11,7 @@
import java.util.ListIterator;
public interface InstructionListIterator
- extends ListIterator<Instruction>,
- NextUntilIterator<Instruction>,
- PreviousUntilIterator<Instruction> {
+ extends InstructionIterator, PreviousUntilIterator<Instruction> {
/**
* Peek the previous instruction.
@@ -50,27 +48,6 @@
}
/**
- * Safe removal function that will insert a DebugLocalRead to take over the debug values if any
- * are associated with the current instruction.
- */
- void removeOrReplaceByDebugLocalRead();
-
- /**
- * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
- * {@link #next} with the passed in <code>newInstruction</code>.
- *
- * The current instruction will be completely detached from the instruction stream with uses
- * of its in-values removed.
- *
- * If the current instruction produces an out-value the new instruction must also produce
- * an out-value, and all uses of the current instructions out-value will be replaced by the
- * new instructions out-value.
- *
- * @param newInstruction the instruction to insert instead of the current.
- */
- void replaceCurrentInstruction(Instruction newInstruction);
-
- /**
* Split the block into two blocks at the point of the {@link ListIterator} cursor. The existing
* block will have all the instructions before the cursor, and the new block all the
* instructions after the cursor.
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 0343e8a..edd33c8 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
@@ -89,12 +89,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -149,10 +147,6 @@
private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
private DexString highestSortingString;
- // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve
- // the current class being optimized.
- private ConcurrentHashMap<DexType, DexProgramClass> cachedClasses = new ConcurrentHashMap<>();
-
// The argument `appView` is only available when full program optimizations are allowed
// (i.e., when running R8).
private IRConverter(
@@ -352,30 +346,24 @@
InterfaceMethodRewriter.Flavor includeAllResources,
ExecutorService executorService)
throws ExecutionException {
- desugarInterfaceMethods(builder, includeAllResources, executorService, null);
- }
-
- private void desugarInterfaceMethods(
- Builder<?> builder,
- InterfaceMethodRewriter.Flavor includeAllResources,
- ExecutorService executorService,
- Map<DexType, DexProgramClass> synthesizedClasses)
- throws ExecutionException {
if (interfaceMethodRewriter != null) {
interfaceMethodRewriter.desugarInterfaceMethods(
- builder, includeAllResources, executorService, synthesizedClasses);
+ builder, includeAllResources, executorService);
}
}
- private void synthesizeTwrCloseResourceUtilityClass(Builder<?> builder) {
+ private void synthesizeTwrCloseResourceUtilityClass(
+ Builder<?> builder, ExecutorService executorService)
+ throws ExecutionException {
if (twrCloseResourceRewriter != null) {
- twrCloseResourceRewriter.synthesizeUtilityClass(builder, options);
+ twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
}
}
- private void synthesizeJava8UtilityClass(Builder<?> builder) {
+ private void synthesizeJava8UtilityClass(
+ Builder<?> builder, ExecutorService executorService) throws ExecutionException {
if (java8MethodRewriter != null) {
- java8MethodRewriter.synthesizeUtilityClass(builder, options);
+ java8MethodRewriter.synthesizeUtilityClass(builder, executorService, options);
}
}
@@ -398,8 +386,8 @@
synthesizeLambdaClasses(builder, executor);
desugarInterfaceMethods(builder, ExcludeDexResources, executor);
- synthesizeTwrCloseResourceUtilityClass(builder);
- synthesizeJava8UtilityClass(builder);
+ synthesizeTwrCloseResourceUtilityClass(builder, executor);
+ synthesizeJava8UtilityClass(builder, executor);
processCovariantReturnTypeAnnotations(builder);
handleSynthesizedClassMapping(builder);
@@ -590,21 +578,20 @@
synthesizeLambdaClasses(builder, executorService);
printPhase("Interface method desugaring");
- Map<DexType, DexProgramClass> synthesizedClasses = new IdentityHashMap<>();
- desugarInterfaceMethods(builder, IncludeAllResources, executorService, synthesizedClasses);
+ desugarInterfaceMethods(builder, IncludeAllResources, executorService);
printPhase("Twr close resource utility class synthesis");
- synthesizeTwrCloseResourceUtilityClass(builder);
- synthesizeJava8UtilityClass(builder);
+ synthesizeTwrCloseResourceUtilityClass(builder, executorService);
+ synthesizeJava8UtilityClass(builder, executorService);
handleSynthesizedClassMapping(builder);
printPhase("Lambda merging finalization");
- finalizeLambdaMerging(application, feedback, builder, executorService, synthesizedClasses);
+ finalizeLambdaMerging(application, feedback, builder, executorService);
if (outliner != null) {
printPhase("Outlining");
timing.begin("IR conversion phase 2");
- if (outliner.selectMethodsForOutlining(synthesizedClasses)) {
+ if (outliner.selectMethodsForOutlining()) {
forEachSelectedOutliningMethod(
executorService,
(code, method) -> {
@@ -612,7 +599,8 @@
outliner.identifyOutlineSites(code, method);
});
DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
- optimizeSynthesizedClass(outlineClass);
+ appInfo.addSynthesizedClass(outlineClass);
+ optimizeSynthesizedClass(outlineClass, executorService);
forEachSelectedOutliningMethod(
executorService,
(code, method) -> {
@@ -636,6 +624,12 @@
uninstantiatedTypeOptimization.logResults();
}
+ // Check if what we've added to the application builder as synthesized classes are same as
+ // what we've added and used through AppInfo.
+ assert appInfo.getSynthesizedClassesForSanityCheck()
+ .containsAll(builder.getSynthesizedClasses())
+ && builder.getSynthesizedClasses()
+ .containsAll(appInfo.getSynthesizedClassesForSanityCheck());
return builder.build();
}
@@ -687,14 +681,13 @@
private void finalizeLambdaMerging(
DexApplication application,
- OptimizationFeedback directFeedback,
+ OptimizationFeedback feedback,
Builder<?> builder,
- ExecutorService executorService,
- Map<DexType, DexProgramClass> synthesizedClasses)
+ ExecutorService executorService)
throws ExecutionException {
if (lambdaMerger != null) {
lambdaMerger.applyLambdaClassMapping(
- application, this, directFeedback, builder, executorService, synthesizedClasses);
+ application, this, feedback, builder, executorService);
}
}
@@ -744,49 +737,24 @@
return result;
}
- public DexClass definitionFor(DexType type) {
- DexProgramClass cached = cachedClasses.get(type);
- return cached != null ? cached : appInfo.definitionFor(type);
- }
-
- public void optimizeSynthesizedClass(DexProgramClass clazz) {
- try {
- enterCachedClass(clazz);
- // Process the generated class, but don't apply any outlining.
- clazz.forEachMethod(this::optimizeSynthesizedMethod);
- } finally {
- leaveCachedClass(clazz);
- }
+ public void optimizeSynthesizedClass(
+ DexProgramClass clazz, ExecutorService executorService)
+ throws ExecutionException {
+ Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
+ clazz.forEachMethod(methods::add);
+ // Process the generated class, but don't apply any outlining.
+ optimizeSynthesizedMethodsConcurrently(methods, executorService);
}
public void optimizeSynthesizedClasses(
Collection<DexProgramClass> classes, ExecutorService executorService)
throws ExecutionException {
Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
- try {
- for (DexProgramClass clazz : classes) {
- enterCachedClass(clazz);
- clazz.forEachMethod(methods::add);
- }
- // Process the generated class, but don't apply any outlining.
- optimizeSynthesizedMethods(methods, executorService);
- } finally {
- for (DexProgramClass clazz : classes) {
- leaveCachedClass(clazz);
- }
+ for (DexProgramClass clazz : classes) {
+ clazz.forEachMethod(methods::add);
}
- }
-
- public void optimizeMethodOnSynthesizedClass(DexProgramClass clazz, DexEncodedMethod method) {
- if (!method.isProcessed()) {
- try {
- enterCachedClass(clazz);
- // Process the generated method, but don't apply any outlining.
- optimizeSynthesizedMethod(method);
- } finally {
- leaveCachedClass(clazz);
- }
- }
+ // Process the generated class, but don't apply any outlining.
+ optimizeSynthesizedMethodsConcurrently(methods, executorService);
}
public void optimizeSynthesizedMethod(DexEncodedMethod method) {
@@ -801,7 +769,7 @@
}
}
- public void optimizeSynthesizedMethods(
+ public void optimizeSynthesizedMethodsConcurrently(
Collection<DexEncodedMethod> methods, ExecutorService executorService)
throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
@@ -821,16 +789,6 @@
ThreadUtils.awaitFutures(futures);
}
- private void enterCachedClass(DexProgramClass clazz) {
- DexProgramClass previous = cachedClasses.put(clazz.type, clazz);
- assert previous == null;
- }
-
- private void leaveCachedClass(DexProgramClass clazz) {
- DexProgramClass existing = cachedClasses.remove(clazz.type);
- assert existing == clazz;
- }
-
private String logCode(InternalOptions options, DexEncodedMethod method) {
return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
}
@@ -1191,7 +1149,8 @@
// original method signature (this could have changed as a result of, for example, class
// merging). Then, we find the type that now corresponds to the the original holder.
DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
- DexClass originalHolder = definitionFor(graphLense().lookupType(originalSignature.holder));
+ DexClass originalHolder = appInfo.definitionFor(
+ graphLense().lookupType(originalSignature.holder));
if (originalHolder.hasKotlinInfo()) {
KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
if (kotlinInfo.hasNonNullParameterHints()) {
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 7c22bbf..fd968d6 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
@@ -210,7 +210,15 @@
iterator.replaceCurrentInstruction(newInvoke);
if (constantReturnMaterializingInstruction != null) {
- iterator.add(constantReturnMaterializingInstruction);
+ if (block.hasCatchHandlers()) {
+ // Split the block to ensure no instructions after throwing instructions.
+ iterator
+ .split(code, blocks)
+ .listIterator()
+ .add(constantReturnMaterializingInstruction);
+ } else {
+ iterator.add(constantReturnMaterializingInstruction);
+ }
}
DexType actualReturnType = actualTarget.proto.returnType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 968b200..a27b096 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -60,7 +60,7 @@
DexType superType = clazz.superType;
// If superClass definition is missing, just skip this part and let real processing of its
// subclasses report the error if it is required.
- DexClass superClass = superType == null ? null : rewriter.findDefinitionFor(superType);
+ DexClass superClass = superType == null ? null : rewriter.appInfo.definitionFor(superType);
if (superClass != null && superType != rewriter.factory.objectType) {
if (superClass.isInterface()) {
throw new CompilationError("Interface `" + superClass.toSourceString()
@@ -96,10 +96,10 @@
private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
DexMethod method = defaultMethod.method;
+ DexClass target = rewriter.appInfo.definitionFor(method.holder);
// NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
// even if this results in invalid code, these classes are never desugared.
- assert rewriter.findDefinitionFor(method.holder) != null
- && !rewriter.findDefinitionFor(method.holder).isLibraryClass();
+ assert target != null && !target.isLibraryClass();
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
DexMethod newMethod = rewriter.factory.createMethod(clazz.type, method.proto, method.name);
@@ -167,7 +167,7 @@
if (current.superType == null) {
break;
} else {
- DexClass superClass = rewriter.findDefinitionFor(current.superType);
+ DexClass superClass = rewriter.appInfo.definitionFor(current.superType);
if (superClass != null) {
current = superClass;
} else {
@@ -205,7 +205,7 @@
DexType superType = current.superType;
DexClass superClass = null;
if (superType != null) {
- superClass = rewriter.findDefinitionFor(superType);
+ superClass = rewriter.appInfo.definitionFor(superType);
// It's available or we would have failed while analyzing the hierarchy for interfaces.
assert superClass != null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index e79017d..abd38a5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication.Builder;
@@ -80,6 +81,7 @@
public static final String PRIVATE_METHOD_PREFIX = "$private$";
private final AppView<? extends AppInfoWithLiveness> appView;
+ final AppInfo appInfo;
private final IRConverter converter;
private final InternalOptions options;
final DexItemFactory factory;
@@ -121,6 +123,7 @@
assert converter != null;
this.appView = appView;
this.converter = converter;
+ this.appInfo = converter.appInfo;
this.options = options;
this.factory = options.itemFactory;
}
@@ -156,7 +159,7 @@
if (instruction.isInvokeStatic()) {
InvokeStatic invokeStatic = instruction.asInvokeStatic();
DexMethod method = invokeStatic.getInvokedMethod();
- DexClass clazz = findDefinitionFor(method.holder);
+ DexClass clazz = appInfo.definitionFor(method.holder);
if (Java8MethodRewriter.hasJava8MethodRewritePrefix(method.holder)) {
// We did not create this code yet, but it will not require rewriting.
continue;
@@ -187,7 +190,7 @@
invokeStatic.outValue(), invokeStatic.arguments()));
requiredDispatchClasses
.computeIfAbsent(clazz.asLibraryClass(), k -> Sets.newConcurrentHashSet())
- .add(findDefinitionFor(encodedMethod.method.holder).asProgramClass());
+ .add(appInfo.definitionFor(encodedMethod.method.holder).asProgramClass());
}
} else {
instructions.replaceCurrentInstruction(
@@ -201,7 +204,7 @@
if (instruction.isInvokeSuper()) {
InvokeSuper invokeSuper = instruction.asInvokeSuper();
DexMethod method = invokeSuper.getInvokedMethod();
- DexClass clazz = findDefinitionFor(method.holder);
+ DexClass clazz = appInfo.definitionFor(method.holder);
if (clazz == null) {
// NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
// exception but we can not report it as error since it can also be the intended
@@ -218,7 +221,7 @@
// WARNING: This may result in incorrect code on older platforms!
// Retarget call to an appropriate method of companion class.
DexMethod amendedMethod = amendDefaultMethod(
- findDefinitionFor(encodedMethod.method.holder), method);
+ appInfo.definitionFor(encodedMethod.method.holder), method);
instructions.replaceCurrentInstruction(
new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
invokeSuper.outValue(), invokeSuper.arguments()));
@@ -233,7 +236,7 @@
continue;
}
- DexClass clazz = findDefinitionFor(method.holder);
+ DexClass clazz = appInfo.definitionFor(method.holder);
if (clazz == null) {
// Report missing class since we don't know if it is an interface.
warnMissingType(encodedMethod.method, method.holder);
@@ -279,7 +282,7 @@
private void reportStaticInterfaceMethodHandle(DexMethod referencedFrom, DexMethodHandle handle) {
if (handle.type.isInvokeStatic()) {
- DexClass holderClass = findDefinitionFor(handle.asMethod().holder);
+ DexClass holderClass = appInfo.definitionFor(handle.asMethod().holder);
// NOTE: If the class definition is missing we can't check. Let it be handled as any other
// missing call target.
if (holderClass == null) {
@@ -292,15 +295,6 @@
}
}
- /**
- * Returns the class definition for the specified type.
- *
- * @return may return null if no definition for the given type is available.
- */
- final DexClass findDefinitionFor(DexType type) {
- return converter.definitionFor(type);
- }
-
// Gets the companion class for the interface `type`.
final DexType getCompanionClassType(DexType type) {
assert type.isClassType();
@@ -334,7 +328,7 @@
}
private boolean isInMainDexList(DexType iface) {
- return converter.appInfo.isInMainDexList(iface);
+ return appInfo.isInMainDexList(iface);
}
// Represent a static interface method as a method of companion class.
@@ -393,8 +387,7 @@
public void desugarInterfaceMethods(
Builder<?> builder,
Flavor flavour,
- ExecutorService executorService,
- Map<DexType, DexProgramClass> synthesizedClasses)
+ ExecutorService executorService)
throws ExecutionException {
// Process all classes first. Add missing forwarding methods to
// replace desugared default interface methods.
@@ -409,13 +402,10 @@
// are just moved from interfaces and don't need to be re-processed.
DexProgramClass synthesizedClass = entry.getValue();
builder.addSynthesizedClass(synthesizedClass, isInMainDexList(entry.getKey()));
-
- if (synthesizedClasses != null) {
- synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
- }
+ appInfo.addSynthesizedClass(synthesizedClass);
}
- converter.optimizeSynthesizedMethods(synthesizedMethods, executorService);
+ converter.optimizeSynthesizedMethodsConcurrently(synthesizedMethods, executorService);
// Cached data is not needed any more.
clear();
@@ -528,7 +518,7 @@
if (isCompanionClassType(holder)) {
holder = getInterfaceClassType(holder);
}
- DexClass clazz = converter.appInfo.definitionFor(holder);
+ DexClass clazz = appInfo.definitionFor(holder);
return clazz == null ? Origin.unknown() : clazz.getOrigin();
}
@@ -550,7 +540,7 @@
DexClass implementing,
DexType iface) {
DefaultMethodsHelper helper = new DefaultMethodsHelper();
- DexClass definedInterface = findDefinitionFor(iface);
+ DexClass definedInterface = appInfo.definitionFor(iface);
if (definedInterface == null) {
warnMissingInterface(classToDesugar, implementing, iface);
return helper.wrapInCollection();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 9d5e5fc..58ec9ed 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -322,7 +322,7 @@
if (!seenBefore.add(superType)) {
continue;
}
- DexClass clazz = rewriter.findDefinitionFor(superType);
+ DexClass clazz = rewriter.appInfo.definitionFor(superType);
if (clazz != null) {
if (clazz.lookupVirtualMethod(method.method) != null) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
index 9e2e9ab..e930bcf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -37,9 +37,12 @@
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.BiFunction;
public final class Java8MethodRewriter {
+ public static final String UTILITY_CLASS_NAME_PREFIX = "$r8$java8methods$utility";
private static final String UTILITY_CLASS_DESCRIPTOR_PREFIX = "L$r8$java8methods$utility";
private final Set<DexType> holders = Sets.newConcurrentHashSet();
private final IRConverter converter;
@@ -88,7 +91,9 @@
return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
}
- public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
+ public void synthesizeUtilityClass(
+ Builder<?> builder, ExecutorService executorService, InternalOptions options)
+ throws ExecutionException {
if (holders.isEmpty()) {
return;
}
@@ -134,7 +139,8 @@
code.setUpContext(utilityClass);
boolean addToMainDexList = referencingClasses.stream()
.anyMatch(clazz -> converter.appInfo.isInMainDexList(clazz.type));
- converter.optimizeSynthesizedClass(utilityClass);
+ converter.appInfo.addSynthesizedClass(utilityClass);
+ converter.optimizeSynthesizedClass(utilityClass, executorService);
builder.addSynthesizedClass(utilityClass, addToMainDexList);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 865a62c..cff5392 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -153,8 +153,9 @@
synthesizeVirtualMethods(mainMethod),
rewriter.factory.getSkipNameValidationForTesting());
// Optimize main method.
- rewriter.converter.optimizeMethodOnSynthesizedClass(
- clazz, clazz.lookupVirtualMethod(mainMethod));
+ rewriter.converter.appInfo.addSynthesizedClass(clazz);
+ rewriter.converter.optimizeSynthesizedMethod(clazz.lookupVirtualMethod(mainMethod));
+
// The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
// ModificationException we must use synchronization.
synchronized (synthesizedFrom) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index ade6feb..d0faabc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -180,14 +180,15 @@
/** Generates lambda classes and adds them to the builder. */
public void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
throws ExecutionException {
+ for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
+ DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
+ appInfo.addSynthesizedClass(synthesizedClass);
+ builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
+ }
converter.optimizeSynthesizedClasses(
knownLambdaClasses.values().stream()
.map(LambdaClass::getLambdaClass).collect(ImmutableSet.toImmutableSet()),
executorService);
- for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
- builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
- }
}
public Set<DexCallSite> getDesugaredCallSites() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index e1e534e..c06e158 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -31,6 +31,8 @@
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
// Try with resources outlining processor. Handles $closeResource methods
// synthesized by java 9 compiler.
@@ -45,6 +47,7 @@
// tree shaking to remove them since now they should not be referenced.
//
public final class TwrCloseResourceRewriter {
+ public static final String UTILITY_CLASS_NAME = "$r8$twr$utility";
public static final String UTILITY_CLASS_DESCRIPTOR = "L$r8$twr$utility;";
private final IRConverter converter;
@@ -107,7 +110,9 @@
&& original.proto == converter.appInfo.dexItemFactory.twrCloseResourceMethodProto;
}
- public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
+ public void synthesizeUtilityClass(
+ Builder<?> builder, ExecutorService executorService, InternalOptions options)
+ throws ExecutionException {
if (referencingClasses.isEmpty()) {
return;
}
@@ -144,7 +149,8 @@
// Process created class and method.
boolean addToMainDexList = referencingClasses.stream()
.anyMatch(clazz -> converter.appInfo.isInMainDexList(clazz.type));
- converter.optimizeSynthesizedClass(utilityClass);
+ converter.appInfo.addSynthesizedClass(utilityClass);
+ converter.optimizeSynthesizedClass(utilityClass, executorService);
builder.addSynthesizedClass(utilityClass, addToMainDexList);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index a07410c..e723a55 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1843,7 +1843,7 @@
return;
}
- DexClass clazz = definitionFor(method.method.getHolder());
+ DexClass clazz = appInfo.definitionFor(method.method.getHolder());
if (clazz == null) {
return;
}
@@ -2044,10 +2044,6 @@
}
}
- DexClass definitionFor(DexType type) {
- return converter.definitionFor(type);
- }
-
public void removeTrivialCheckCastAndInstanceOfInstructions(
IRCode code, boolean enableWholeProgramOptimizations) {
if (!enableWholeProgramOptimizations) {
@@ -2153,7 +2149,7 @@
if (baseType.isPrimitiveType()) {
return false;
}
- DexClass clazz = definitionFor(baseType);
+ DexClass clazz = appInfo.definitionFor(baseType);
if (clazz == null) {
// Conservatively say yes.
return true;
@@ -3502,7 +3498,7 @@
if (type == dexItemFactory.throwableType) {
return true;
}
- DexClass dexClass = definitionFor(type);
+ DexClass dexClass = appInfo.definitionFor(type);
if (dexClass == null) {
throw new CompilationError("Class or interface " + type.toSourceString() +
" required for desugaring of try-with-resources is not found.");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 434bb04..7ebc21c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,18 +17,20 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardMemberRule;
import com.google.common.collect.Sets;
+import java.util.ListIterator;
import java.util.Set;
import java.util.function.Predicate;
@@ -114,174 +116,207 @@
}
}
- private void replaceInstructionFromProguardRule(RuleType ruleType, InstructionIterator iterator,
- Instruction current, Instruction replacement) {
- if (ruleType == RuleType.ASSUME_NO_SIDE_EFFECTS) {
+ private boolean tryConstantReplacementFromProguard(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ Instruction current,
+ ProguardMemberRuleLookup lookup) {
+ Instruction replacement = constantReplacementFromProguardRule(lookup.rule, code, current);
+ if (replacement == null) {
+ // Check to see if a value range can be assumed.
+ setValueRangeFromProguardRule(lookup.rule, current.outValue());
+ return false;
+ }
+ affectedValues.add(replacement.outValue());
+ if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) {
iterator.replaceCurrentInstruction(replacement);
} else {
+ assert lookup.type == RuleType.ASSUME_VALUES;
if (current.outValue() != null) {
assert replacement.outValue() != null;
current.outValue().replaceUsers(replacement.outValue());
}
replacement.setPosition(current.getPosition());
- iterator.add(replacement);
+ if (current.getBlock().hasCatchHandlers()) {
+ iterator.split(code, blocks).listIterator().add(replacement);
+ } else {
+ iterator.add(replacement);
+ }
+ }
+ return true;
+ }
+
+ private void rewriteInvokeMethodWithConstantValues(
+ IRCode code,
+ DexType callingContext,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ InvokeMethod current) {
+ DexMethod invokedMethod = current.getInvokedMethod();
+ DexType invokedHolder = invokedMethod.getHolder();
+ if (!invokedHolder.isClassType()) {
+ return;
+ }
+ // TODO(70550443): Maybe check all methods here.
+ DexEncodedMethod definition = appInfo.lookup(current.getType(), invokedMethod, callingContext);
+ ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
+ boolean invokeReplaced = false;
+ if (lookup != null) {
+ boolean outValueNullOrNotUsed = current.outValue() == null || !current.outValue().isUsed();
+ if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS && outValueNullOrNotUsed) {
+ // Remove invoke if marked as having no side effects and the return value is not used.
+ iterator.removeOrReplaceByDebugLocalRead();
+ invokeReplaced = true;
+ } else if (!outValueNullOrNotUsed) {
+ // Check to see if a constant value can be assumed.
+ invokeReplaced =
+ tryConstantReplacementFromProguard(
+ code, affectedValues, blocks, iterator, current, lookup);
+ }
+ }
+ if (invokeReplaced || current.outValue() == null) {
+ return;
+ }
+ // No Proguard rule could replace the instruction check for knowledge about the return value.
+ DexEncodedMethod target = current.lookupSingleTarget(appInfo, callingContext);
+ if (target == null) {
+ return;
+ }
+ if (target.getOptimizationInfo().neverReturnsNull() && current.outValue().canBeNull()) {
+ Value knownToBeNonNullValue = current.outValue();
+ knownToBeNonNullValue.markNeverNull();
+ TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+ }
+ if (target.getOptimizationInfo().returnsConstant()) {
+ long constant = target.getOptimizationInfo().getReturnedConstant();
+ ConstNumber replacement =
+ createConstNumberReplacement(
+ code, constant, current.outValue().getTypeLattice(), current.getLocalInfo());
+ affectedValues.add(replacement.outValue());
+ current.outValue().replaceUsers(replacement.outValue());
+ current.setOutValue(null);
+ replacement.setPosition(current.getPosition());
+ current.moveDebugValues(replacement);
+ if (current.getBlock().hasCatchHandlers()) {
+ iterator.split(code, blocks).listIterator().add(replacement);
+ } else {
+ iterator.add(replacement);
+ }
+ }
+ }
+
+ private void rewriteStaticGetWithConstantValues(
+ IRCode code,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ StaticGet current) {
+ DexField field = current.getField();
+
+ // TODO(b/123857022): Should be able to use definitionFor().
+ DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+ if (target == null) {
+ return;
+ }
+ // Check if a this value is known const.
+ Instruction replacement = target.valueAsConstInstruction(appInfo, current.dest());
+ if (replacement != null) {
+ affectedValues.add(replacement.outValue());
+ iterator.replaceCurrentInstruction(replacement);
+ return;
+ }
+ ProguardMemberRuleLookup lookup = lookupMemberRule(target);
+ if (lookup != null
+ && lookup.type == RuleType.ASSUME_VALUES
+ && tryConstantReplacementFromProguard(
+ code, affectedValues, blocks, iterator, current, lookup)) {
+ return;
+ }
+ if (current.dest() != null) {
+ // In case the class holder of this static field satisfying following criteria:
+ // -- cannot trigger other static initializer except for its own
+ // -- is final
+ // -- has a class initializer which is classified as trivial
+ // (see CodeRewriter::computeClassInitializerInfo) and
+ // initializes the field being accessed
+ //
+ // ... and the field itself is not pinned by keep rules (in which case it might
+ // be updated outside the class constructor, e.g. via reflections), it is safe
+ // to assume that the static-get instruction reads the value it initialized value
+ // in class initializer and is never null.
+ DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
+ if (holderDefinition != null
+ && holderDefinition.accessFlags.isFinal()
+ && !field.getHolder().initializationOfParentTypesMayHaveSideEffects(appInfo)) {
+ Value outValue = current.dest();
+ DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+ if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+ TrivialInitializer info =
+ classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+ if (info != null
+ && ((TrivialClassInitializer) info).field == field
+ && !appInfo.isPinned(field)
+ && outValue.canBeNull()) {
+ outValue.markNeverNull();
+ TypeLatticeElement typeLattice = outValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ outValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(outValue.affectedValues());
+ }
+ }
+ }
+ }
+ }
+
+ private void rewritePutWithConstantValues(
+ InstructionIterator iterator, FieldInstruction current) {
+ DexField field = current.getField();
+ // TODO(b/123857022): Should be possible to use definitionFor().
+ DexEncodedField target =
+ current.isInstancePut()
+ ? appInfo.lookupInstanceTarget(field.getHolder(), field)
+ : appInfo.lookupStaticTarget(field.getHolder(), field);
+ // TODO(b/123857022): Should be possible to use `!isFieldRead(field)`.
+ if (target != null && !appInfo.isFieldRead(target.field)) {
+ // Remove writes to dead (i.e. never read) fields.
+ iterator.removeOrReplaceByDebugLocalRead();
}
}
/**
* Replace invoke targets and field accesses with constant values where possible.
- * <p>
- * Also assigns value ranges to values where possible.
+ *
+ * <p>Also assigns value ranges to values where possible.
*/
public void rewriteWithConstantValues(
IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
- InstructionIterator iterator = code.instructionIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType invokedHolder = invokedMethod.getHolder();
- if (!invokedHolder.isClassType()) {
- continue;
- }
- // TODO(70550443): Maybe check all methods here.
- DexEncodedMethod definition = appInfo
- .lookup(invoke.getType(), invokedMethod, callingContext);
-
- boolean invokeReplaced = false;
- ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
- if (lookup != null) {
- if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
- && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
- // Remove invoke if marked as having no side effects and the return value is not used.
- iterator.remove();
- invokeReplaced = true;
- } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
- // Check to see if a constant value can be assumed.
- Instruction replacement =
- constantReplacementFromProguardRule(lookup.rule, code, invoke);
- if (replacement != null) {
- affectedValues.add(replacement.outValue());
- replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
- invokeReplaced = true;
- } else {
- // Check to see if a value range can be assumed.
- setValueRangeFromProguardRule(lookup.rule, current.outValue());
- }
- }
- }
-
- // If no Proguard rule could replace the instruction check for knowledge about the
- // return value.
- if (!invokeReplaced && invoke.outValue() != null) {
- DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
- if (target != null) {
- if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
- Value knownToBeNonNullValue = invoke.outValue();
- knownToBeNonNullValue.markNeverNull();
- TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
- assert typeLattice.isNullable() && typeLattice.isReference();
- knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
- affectedValues.addAll(knownToBeNonNullValue.affectedValues());
- }
- if (target.getOptimizationInfo().returnsConstant()) {
- long constant = target.getOptimizationInfo().getReturnedConstant();
- ConstNumber replacement = createConstNumberReplacement(
- code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
- affectedValues.add(replacement.outValue());
- invoke.outValue().replaceUsers(replacement.outValue());
- invoke.setOutValue(null);
- replacement.setPosition(invoke.getPosition());
- invoke.moveDebugValues(replacement);
- iterator.add(replacement);
- }
- }
- }
- } else if (current.isInstancePut()) {
- InstancePut instancePut = current.asInstancePut();
- DexField field = instancePut.getField();
- DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
- if (target != null) {
- // Remove writes to dead (i.e. never read) fields.
- if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
- iterator.remove();
- }
- }
- } else if (current.isStaticGet()) {
- StaticGet staticGet = current.asStaticGet();
- DexField field = staticGet.getField();
- DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
- ProguardMemberRuleLookup lookup = null;
- if (target != null) {
- // Check if a this value is known const.
- Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
- if (replacement == null) {
- lookup = lookupMemberRule(target);
- if (lookup != null) {
- replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
- }
- }
- if (replacement == null) {
- // If no const replacement was found, at least store the range information.
- if (lookup != null) {
- setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
- }
- }
- if (replacement != null) {
- affectedValues.add(replacement.outValue());
- // Ignore assumenosideeffects for fields.
- if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
- replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
- } else {
- iterator.replaceCurrentInstruction(replacement);
- }
- } else if (staticGet.dest() != null) {
- // In case the class holder of this static field satisfying following criteria:
- // -- cannot trigger other static initializer except for its own
- // -- is final
- // -- has a class initializer which is classified as trivial
- // (see CodeRewriter::computeClassInitializerInfo) and
- // initializes the field being accessed
- //
- // ... and the field itself is not pinned by keep rules (in which case it might
- // be updated outside the class constructor, e.g. via reflections), it is safe
- // to assume that the static-get instruction reads the value it initialized value
- // in class initializer and is never null.
- //
- DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
- if (holderDefinition != null
- && holderDefinition.accessFlags.isFinal()
- && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
- Value outValue = staticGet.dest();
- DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
- if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
- TrivialInitializer info =
- classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
- if (info != null
- && ((TrivialClassInitializer) info).field == field
- && !appInfo.isPinned(field)
- && outValue.canBeNull()) {
- outValue.markNeverNull();
- TypeLatticeElement typeLattice = outValue.getTypeLattice();
- assert typeLattice.isNullable() && typeLattice.isReference();
- outValue.narrowing(appInfo, typeLattice.asNonNullable());
- affectedValues.addAll(outValue.affectedValues());
- }
- }
- }
- }
- }
- } else if (current.isStaticPut()) {
- StaticPut staticPut = current.asStaticPut();
- DexField field = staticPut.getField();
- DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
- if (target != null) {
- // Remove writes to dead (i.e. never read) fields.
- if (!isFieldRead(target, true)) {
- iterator.removeOrReplaceByDebugLocalRead();
- }
+ ListIterator<BasicBlock> blocks = code.blocks.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isInvokeMethod()) {
+ rewriteInvokeMethodWithConstantValues(
+ code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
+ } else if (current.isInstancePut() || current.isStaticPut()) {
+ rewritePutWithConstantValues(iterator, current.asFieldInstruction());
+ } else if (current.isStaticGet()) {
+ rewriteStaticGetWithConstantValues(
+ code,
+ isProcessedConcurrently,
+ affectedValues,
+ blocks,
+ iterator,
+ current.asStaticGet());
}
}
}
@@ -290,14 +325,4 @@
}
assert code.isConsistentSSA();
}
-
- private boolean isFieldRead(DexEncodedField field, boolean isStatic) {
- if (appInfo.fieldsRead.contains(field.field)
- || appInfo.isPinned(field.field)) {
- return true;
- }
- // For library classes we don't know whether a field is read.
- DexClass holder = appInfo.definitionFor(field.field.clazz);
- return holder == null || holder.isLibraryClass();
- }
}
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 87b2537..452f4ed 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
@@ -1228,7 +1228,7 @@
}
}
- public boolean selectMethodsForOutlining(Map<DexType, DexProgramClass> synthesizedClasses) {
+ public boolean selectMethodsForOutlining() {
assert methodsSelectedForOutlining.size() == 0;
assert outlineSites.size() == 0;
for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
@@ -1237,7 +1237,7 @@
methodsSelectedForOutlining.add(
converter
.graphLense()
- .mapDexEncodedMethod(outlineMethod, appInfo, synthesizedClasses));
+ .mapDexEncodedMethod(outlineMethod, appInfo));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 2972ffd..1f4c1c3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -234,6 +234,6 @@
}
// Check for static initializers in this class or any of interfaces it implements.
- return !appInfo.canTriggerStaticInitializer(clazz, true);
+ return !clazz.initializationOfParentTypesMayHaveSideEffects(appInfo);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index cdf37c4..d6ca0e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -116,12 +116,14 @@
if (!eligibleClass.isClassType()) {
return false;
}
- eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
- if (eligibleClassDefinition == null && lambdaRewriter != null) {
+ if (lambdaRewriter != null) {
// Check if the class is synthesized for a desugared lambda
eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
isDesugaredLambda = eligibleClassDefinition != null;
}
+ if (eligibleClassDefinition == null) {
+ eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+ }
return eligibleClassDefinition != null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index b988a89..ab6f635 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
@@ -36,6 +37,7 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@@ -194,8 +196,7 @@
IRConverter converter,
OptimizationFeedback feedback,
Builder<?> builder,
- ExecutorService executorService,
- Map<DexType, DexProgramClass> synthesizedClasses)
+ ExecutorService executorService)
throws ExecutionException {
if (lambdas.isEmpty()) {
return;
@@ -218,18 +219,29 @@
this.strategyFactory = ApplyStrategy::new;
// Add synthesized lambda group classes to the builder.
- converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
DexProgramClass synthesizedClass = entry.getValue();
- synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
+ converter.appInfo.addSynthesizedClass(synthesizedClass);
builder.addSynthesizedClass(
synthesizedClass, entry.getKey().shouldAddToMainDex(converter.appInfo));
+ // Eventually, we need to process synthesized methods in the lambda group.
+ // Otherwise, abstract SynthesizedCode will be flown to Enqueuer.
+ // But that process should not see the holder. Otherwise, lambda calls in the main dispatch
+ // method became recursive calls via the lense rewriter. They should remain, then inliner
+ // will inline methods from mergee lambdas to the main dispatch method.
+ // Then, there is a dilemma: other sub optimizations trigger subtype lookup that will throw
+ // NPE if it cannot find the holder for this synthesized lambda group.
+ // One hack here is to mark those methods `processed` so that the lense rewriter is skipped.
+ synthesizedClass.forEachMethod(encodedMethod -> {
+ encodedMethod.markProcessed(ConstraintWithTarget.NEVER);
+ });
}
+ converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
// Rewrite lambda class references into lambda group class
// references inside methods from the processing queue.
- rewriteLambdaReferences(converter, synthesizedClasses, feedback);
+ rewriteLambdaReferences(converter, feedback);
this.strategyFactory = null;
}
@@ -304,10 +316,7 @@
}
}
- private void rewriteLambdaReferences(
- IRConverter converter,
- Map<DexType, DexProgramClass> synthesizedClasses,
- OptimizationFeedback feedback) {
+ private void rewriteLambdaReferences(IRConverter converter, OptimizationFeedback feedback) {
List<DexEncodedMethod> methods =
methodsToReprocess
.stream()
@@ -315,9 +324,9 @@
.collect(Collectors.toList());
for (DexEncodedMethod method : methods) {
DexEncodedMethod mappedMethod =
- converter.graphLense().mapDexEncodedMethod(method, converter.appInfo, synthesizedClasses);
+ converter.graphLense().mapDexEncodedMethod(method, converter.appInfo);
converter.processMethod(mappedMethod, feedback,
- x -> false, CallSiteInformation.empty(), Outliner::noProcessing);
+ Predicates.alwaysFalse(), CallSiteInformation.empty(), Outliner::noProcessing);
assert mappedMethod.isProcessed();
}
}
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 d08f3e6..e4d2cec 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1980,11 +1980,11 @@
/**
* Set of all fields which may be touched by a get operation. This is actual field definitions.
*/
- public final SortedSet<DexField> fieldsRead;
+ private final SortedSet<DexField> fieldsRead;
/**
* Set of all fields which may be touched by a put operation. This is actual field definitions.
*/
- public final SortedSet<DexField> fieldsWritten;
+ private final SortedSet<DexField> fieldsWritten;
/**
* Set of all field ids used in instance field reads, along with access context.
*/
@@ -2354,7 +2354,8 @@
public boolean isInstantiatedDirectly(DexType type) {
assert type.isClassType();
- return instantiatedTypes.contains(type)
+ return type.isD8R8SynthesizedClassType()
+ || instantiatedTypes.contains(type)
|| instantiatedLambdas.contains(type)
|| instantiatedAnnotationTypes.contains(type);
}
@@ -2381,6 +2382,31 @@
return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
}
+ public boolean isFieldRead(DexField field) {
+ return fieldsRead.contains(field)
+ // TODO(b/121354886): Pinned fields should be in `fieldsRead`.
+ || isPinned(field)
+ // Fields in the class that is synthesized by D8/R8 would be used soon.
+ || field.getHolder().isD8R8SynthesizedClassType()
+ // For library classes we don't know whether a field is read.
+ || isLibraryField(field);
+ }
+
+ public boolean isFieldWritten(DexField field) {
+ return fieldsWritten.contains(field)
+ // TODO(b/121354886): Pinned fields should be in `fieldsWritten`.
+ || isPinned(field)
+ // Fields in the class that is synthesized by D8/R8 would be used soon.
+ || field.clazz.isD8R8SynthesizedClassType()
+ // For library classes we don't know whether a field is rewritten.
+ || isLibraryField(field);
+ }
+
+ private boolean isLibraryField(DexField field) {
+ DexClass holder = definitionFor(field.clazz);
+ return holder == null || holder.isLibraryClass();
+ }
+
private Object2BooleanMap<DexReference> joinIdentifierNameStrings(
Set<DexReference> explicit, Set<DexReference> implicit) {
Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index acc1e78..8017878 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -228,8 +228,8 @@
Predicate<DexField> isReachableOrReferencedField =
field ->
appInfo.liveFields.contains(field)
- || appInfo.fieldsRead.contains(field)
- || appInfo.fieldsWritten.contains(field);
+ || appInfo.isFieldRead(field)
+ || appInfo.isFieldWritten(field);
int firstUnreachable =
firstUnreachableIndex(Arrays.asList(fields), isReachableOrReferencedField);
// Return the original array if all fields are used.
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java
new file mode 100644
index 0000000..cc35cbd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java
@@ -0,0 +1,125 @@
+// 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.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.memberrebinding.testclasses.IllegalFieldRebindingTestClasses;
+import com.android.tools.r8.memberrebinding.testclasses.IllegalFieldRebindingTestClasses.B;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalFieldRebindingTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public IllegalFieldRebindingTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = StringUtils.lines("42");
+
+ if (backend == Backend.CF) {
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+ }
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(IllegalFieldRebindingTest.class)
+ .addInnerClasses(IllegalFieldRebindingTestClasses.class)
+ .addKeepMainRule(TestClass.class)
+ .enableMergeAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject methodSubject = classSubject.mainMethod();
+ assertThat(methodSubject, isPresent());
+
+ // Verify that the static-put instruction has not been removed.
+ assertEquals(
+ 1, methodSubject.streamInstructions().filter(InstructionSubject::isStaticPut).count());
+ }
+
+ @Test
+ public void otherTest() throws Exception {
+ String expectedOutput = StringUtils.lines("0");
+
+ if (backend == Backend.CF) {
+ testForJvm()
+ .addTestClasspath()
+ .run(OtherTestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(IllegalFieldRebindingTest.class)
+ .addInnerClasses(IllegalFieldRebindingTestClasses.class)
+ .addKeepMainRule(OtherTestClass.class)
+ .enableMergeAnnotations()
+ .run(OtherTestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ // The static-get instruction in OtherTestClass.main() should have been replaced by the
+ // constant 0, which makes the remaining classes unreachable.
+ assertEquals(1, inspector.allClasses().size());
+
+ ClassSubject classSubject = inspector.clazz(OtherTestClass.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject methodSubject = classSubject.mainMethod();
+ assertThat(methodSubject, isPresent());
+
+ // Verify that a constant 0 is being loaded in main().
+ assertEquals(
+ 1,
+ methodSubject
+ .streamInstructions()
+ .filter(instruction -> instruction.isConstNumber(0))
+ .count());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // Cannot be member rebound because A is inaccessible to this package. However, the
+ // instruction cannot be removed as the field `A.f` is actually read.
+ B.f = 42;
+ B.print();
+ }
+ }
+
+ static class OtherTestClass {
+
+ public static void main(String[] args) {
+ // Cannot be member rebound because A is inaccessible to this package.
+ // However, the instruction should still be replaced by the constant 0.
+ System.out.println(B.f);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java
new file mode 100644
index 0000000..64b4d1f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java
@@ -0,0 +1,22 @@
+// 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.memberrebinding.testclasses;
+
+import com.android.tools.r8.NeverMerge;
+
+public class IllegalFieldRebindingTestClasses {
+
+ @NeverMerge
+ static class A {
+ public static int f;
+ }
+
+ public static class B extends A {
+
+ public static void print() {
+ System.out.println(A.f);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index d8631ee..6731209 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -287,8 +287,7 @@
mainClass,
ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
keepMainProguardConfiguration(mainClass),
- // TODO(74379749): Proguard does not keep the class with the un-initialized static field.
- this::checkAllClassesPresentOnlyMainWithDefaultConstructor,
+ this::checkOnlyMainPresent,
this::checkOnlyMainPresent);
}