Instruction read set analysis
Change-Id: I7d6089119df4a6c7de02fdd5f8a7e589937e8069
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
new file mode 100644
index 0000000..60e799e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
@@ -0,0 +1,44 @@
+// 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.analysis.fieldvalueanalysis;
+
+import com.android.tools.r8.graph.DexEncodedField;
+
+/**
+ * Implements a lifted subset lattice for fields.
+ *
+ * <p>An element can either be:
+ *
+ * <ol>
+ * <li>bottom, represented by {@link EmptyFieldSet},
+ * <li>a (possibly empty) set of fields, represented by {@link ConcreteMutableFieldSet}, or
+ * <li>top, represented by {@link UnknownFieldSet}.
+ * </ol>
+ *
+ * <p>Note that this class currently does not contain a {@code join()} method. Instead, the {@link
+ * ConcreteMutableFieldSet} has an {@link ConcreteMutableFieldSet#add(DexEncodedField)} and {@link
+ * ConcreteMutableFieldSet#addAll(ConcreteMutableFieldSet)} method. This is intentional, since the
+ * use of {@code join()} could lead to an excessive amount of set copying under the hood.
+ */
+public abstract class AbstractFieldSet {
+
+ public boolean isConcreteFieldSet() {
+ return false;
+ }
+
+ public ConcreteMutableFieldSet asConcreteFieldSet() {
+ return null;
+ }
+
+ public abstract boolean contains(DexEncodedField field);
+
+ public boolean isBottom() {
+ return false;
+ }
+
+ public boolean isTop() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
new file mode 100644
index 0000000..975272a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -0,0 +1,50 @@
+// 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.analysis.fieldvalueanalysis;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class ConcreteMutableFieldSet extends AbstractFieldSet {
+
+ private final Set<DexEncodedField> fields;
+
+ public ConcreteMutableFieldSet() {
+ fields = Sets.newIdentityHashSet();
+ }
+
+ public ConcreteMutableFieldSet(DexEncodedField field) {
+ fields = SetUtils.newIdentityHashSet(field);
+ }
+
+ public void add(DexEncodedField field) {
+ fields.add(field);
+ }
+
+ public void addAll(ConcreteMutableFieldSet other) {
+ fields.addAll(other.fields);
+ }
+
+ public int size() {
+ return fields.size();
+ }
+
+ @Override
+ public boolean isConcreteFieldSet() {
+ return true;
+ }
+
+ @Override
+ public ConcreteMutableFieldSet asConcreteFieldSet() {
+ return this;
+ }
+
+ @Override
+ public boolean contains(DexEncodedField field) {
+ return fields.contains(field);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
new file mode 100644
index 0000000..4897aeb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
@@ -0,0 +1,28 @@
+// 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.analysis.fieldvalueanalysis;
+
+import com.android.tools.r8.graph.DexEncodedField;
+
+public class EmptyFieldSet extends AbstractFieldSet {
+
+ private static final EmptyFieldSet INSTANCE = new EmptyFieldSet();
+
+ private EmptyFieldSet() {}
+
+ public static EmptyFieldSet getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean contains(DexEncodedField field) {
+ return false;
+ }
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
new file mode 100644
index 0000000..9df87cc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
@@ -0,0 +1,28 @@
+// 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.analysis.fieldvalueanalysis;
+
+import com.android.tools.r8.graph.DexEncodedField;
+
+public class UnknownFieldSet extends AbstractFieldSet {
+
+ private static final UnknownFieldSet INSTANCE = new UnknownFieldSet();
+
+ private UnknownFieldSet() {}
+
+ public static UnknownFieldSet getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean contains(DexEncodedField field) {
+ return true;
+ }
+
+ @Override
+ public boolean isTop() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
new file mode 100644
index 0000000..57f8d2e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
@@ -0,0 +1,45 @@
+// 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.analysis.modeling;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import java.util.function.Predicate;
+
+/** Models if a given library method may cause a program field to be read. */
+public class LibraryMethodReadSetModeling {
+
+ public static AbstractFieldSet getModeledReadSetOrUnknown(
+ InvokeMethod invoke, DexItemFactory dexItemFactory) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+
+ // Check if it is a library method that does not have side effects. In that case it is safe to
+ // assume that the method does not read any fields, since even if it did, it would not be able
+ // to do anything with the values it read (since we will remove such invocations without side
+ // effects).
+ Predicate<InvokeMethod> noSideEffectsPredicate =
+ dexItemFactory.libraryMethodsWithoutSideEffects.get(invokedMethod);
+ if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(invoke)) {
+ return EmptyFieldSet.getInstance();
+ }
+
+ // Already handled above.
+ assert !dexItemFactory.classMethods.isReflectiveNameLookup(invokedMethod);
+
+ // Modeling of other library methods.
+ DexType holder = invokedMethod.holder;
+ if (holder == dexItemFactory.objectType) {
+ if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+ return EmptyFieldSet.getInstance();
+ }
+ }
+ return UnknownFieldSet.getInstance();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index e041d5a..b95376e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -11,6 +11,10 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.AbstractError;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Collections;
import java.util.List;
@@ -131,4 +135,32 @@
// TODO(jsjeon): what if the target field is known to be non-null?
return true;
}
+
+ @Override
+ public AbstractFieldSet readSet(AppView<?> appView, DexType context) {
+ if (instructionMayTriggerMethodInvocation(appView, context)) {
+ // This may trigger class initialization, which could potentially read any field.
+ return UnknownFieldSet.getInstance();
+ }
+
+ if (isFieldGet()) {
+ DexField field = getField();
+ DexEncodedField encodedField = null;
+ if (appView.enableWholeProgramOptimizations()) {
+ encodedField = appView.appInfo().resolveField(field);
+ } else {
+ DexClass clazz = appView.definitionFor(field.holder);
+ if (clazz != null) {
+ encodedField = clazz.lookupField(field);
+ }
+ }
+ if (encodedField != null) {
+ return new ConcreteMutableFieldSet(encodedField);
+ }
+ return UnknownFieldSet.getInstance();
+ }
+
+ assert isFieldPut();
+ return EmptyFieldSet.getInstance();
+ }
}
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 03421cc..1a17a8b 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
@@ -17,6 +17,9 @@
import com.android.tools.r8.ir.analysis.constant.Bottom;
import com.android.tools.r8.ir.analysis.constant.ConstRangeLatticeElement;
import com.android.tools.r8.ir.analysis.constant.LatticeElement;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
import com.android.tools.r8.ir.code.Assume.NoAssumption;
@@ -540,6 +543,18 @@
}
/**
+ * Returns an abstraction of the set of fields that may possibly be read as a result of executing
+ * this instruction.
+ */
+ public AbstractFieldSet readSet(AppView<?> appView, DexType context) {
+ if (instructionMayTriggerMethodInvocation(appView, context)
+ && instructionMayHaveSideEffects(appView, context)) {
+ return UnknownFieldSet.getInstance();
+ }
+ return EmptyFieldSet.getInstance();
+ }
+
+ /**
* Returns true if this instruction need this value in a register.
*/
public boolean needsValueInRegister(Value value) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 9381fd1..48baef4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -12,11 +12,15 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
+import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
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;
@@ -277,4 +281,22 @@
return true;
}
+
+ @Override
+ public AbstractFieldSet readSet(AppView<?> appView, DexType context) {
+ DexMethod invokedMethod = getInvokedMethod();
+
+ // Trivial instance initializers do not read any fields.
+ if (appView.dexItemFactory().isConstructor(invokedMethod)) {
+ DexEncodedMethod singleTarget = lookupSingleTarget(appView, context);
+ if (singleTarget != null) {
+ TrivialInitializer info = singleTarget.getOptimizationInfo().getTrivialInitializerInfo();
+ if (info != null && info.isTrivialInstanceInitializer()) {
+ return EmptyFieldSet.getInstance();
+ }
+ }
+ }
+
+ return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index c3db7ea..178da3f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -119,4 +121,9 @@
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
return true;
}
+
+ @Override
+ public AbstractFieldSet readSet(AppView<?> appView, DexType context) {
+ return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory());
+ }
}