Use field-never-read-before-write analysis in field assignment tracker
This refactors the current analysis from `FieldValueAnalysis` into its own class `FieldReadBeforeWriteAnalysis` to allow reusing the analysis in the `FieldAssignmentTracker`.
Fixes: b/274802355
Change-Id: I9debd6b0af9cf04fdffa1d9332743c504db59988
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index a664d9d..97ceb6e 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -747,7 +747,7 @@
dataResourceConsumer.accept(adapted, options.reporter);
} else {
options.reporter.warning(
- new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
+ new StringDiagnostic("Resource '" + adapted.getName() + "' already exists."));
}
options.reporter.failIfPendingErrors();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index d871616..637026f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -69,6 +70,7 @@
IRCode code,
BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
OptimizationFeedback feedback,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
MethodProcessor methodProcessor) {
if (!methodProcessor.isPrimaryMethodProcessor()) {
return;
@@ -85,7 +87,8 @@
appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField();
if (field != null) {
if (fieldAssignmentTracker != null) {
- fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field, code.context());
+ fieldAssignmentTracker.recordFieldAccess(
+ fieldInstruction, field, fieldReadBeforeWriteAnalysis);
}
if (fieldBitAccessAnalysis != null) {
fieldBitAccessAnalysis.recordFieldAccess(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 0db5825..dc90a85 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteArrayTypeFieldState;
import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteClassTypeFieldState;
import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcretePrimitiveTypeFieldState;
@@ -34,6 +35,8 @@
import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
@@ -179,16 +182,23 @@
});
}
- void recordFieldAccess(FieldInstruction instruction, ProgramField field, ProgramMethod context) {
+ void recordFieldAccess(
+ FieldInstruction instruction,
+ ProgramField field,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
if (instruction.isFieldPut()) {
- recordFieldPut(field, instruction.value(), context);
+ recordFieldPut(instruction.asFieldPut(), field, fieldReadBeforeWriteAnalysis);
}
}
- private void recordFieldPut(ProgramField field, Value value, ProgramMethod context) {
+ private void recordFieldPut(
+ FieldPut fieldPut,
+ ProgramField field,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
// For now only attempt to prove that fields are definitely null. In order to prove a single
// value for fields that are not definitely null, we need to prove that the given field is never
// read before it is written.
+ Value value = fieldPut.value();
AbstractValue abstractValue =
value.isZero() ? abstractValueFactory.createZeroValue() : AbstractValue.unknown();
fieldStates.compute(
@@ -203,12 +213,8 @@
return ConcretePrimitiveTypeFieldState.create(abstractValue);
}
assert fieldType.isClassType();
- DynamicType dynamicType =
- WideningUtils.widenDynamicNonReceiverType(
- appView,
- value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
- field.getType());
- return ConcreteClassTypeFieldState.create(abstractValue, dynamicType);
+ return ConcreteClassTypeFieldState.create(
+ abstractValue, getDynamicType(fieldPut, field, fieldReadBeforeWriteAnalysis));
}
if (fieldState.isUnknown()) {
@@ -231,10 +237,31 @@
ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
return classFieldState.mutableJoin(
- appView, abstractValue, value.getDynamicType(appView), field);
+ appView,
+ abstractValue,
+ getDynamicType(fieldPut, field, fieldReadBeforeWriteAnalysis),
+ field);
});
}
+ private DynamicType getDynamicType(
+ FieldPut fieldPut,
+ ProgramField field,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
+ DynamicTypeWithUpperBound dynamicType = fieldPut.value().getDynamicType(appView);
+ if (fieldPut.isInstancePut()) {
+ InstancePut instancePut = fieldPut.asInstancePut();
+ if (fieldReadBeforeWriteAnalysis.isInstanceFieldMaybeReadBeforeInstruction(
+ instancePut.object(), field.getDefinition(), instancePut)) {
+ dynamicType = dynamicType.withNullability(Nullability.maybeNull());
+ }
+ } else if (fieldReadBeforeWriteAnalysis.isStaticFieldMaybeReadBeforeInstruction(
+ field.getDefinition(), fieldPut.asStaticPut())) {
+ dynamicType = dynamicType.withNullability(Nullability.maybeNull());
+ }
+ return WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, field.getType());
+ }
+
void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
abstractInstanceFieldValues.get(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysis.java
new file mode 100644
index 0000000..fb6bae0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysis.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.fieldaccess.readbeforewrite;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+
+public abstract class FieldReadBeforeWriteAnalysis {
+
+ public static FieldReadBeforeWriteAnalysis create(
+ AppView<?> appView, IRCode code, ProgramMethod context) {
+ if (appView.hasLiveness()) {
+ return new FieldReadBeforeWriteAnalysisImpl(appView.withLiveness(), code, context);
+ }
+ return trivial();
+ }
+
+ public static TrivialFieldReadBeforeWriteAnalysis trivial() {
+ return new TrivialFieldReadBeforeWriteAnalysis();
+ }
+
+ public abstract boolean isInstanceFieldMaybeReadBeforeInstruction(
+ Value receiver, DexEncodedField field, Instruction instruction);
+
+ public abstract boolean isStaticFieldMaybeReadBeforeInstruction(
+ DexEncodedField field, Instruction instruction);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysisImpl.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysisImpl.java
new file mode 100644
index 0000000..7e7ac89
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysisImpl.java
@@ -0,0 +1,209 @@
+// Copyright (c) 2023, 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.fieldaccess.readbeforewrite;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.ProgramMethod;
+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.KnownFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+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.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DequeUtils;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+class FieldReadBeforeWriteAnalysisImpl extends FieldReadBeforeWriteAnalysis {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final IRCode code;
+ private final ProgramMethod context;
+
+ private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
+
+ public FieldReadBeforeWriteAnalysisImpl(
+ AppView<AppInfoWithLiveness> appView, IRCode code, ProgramMethod context) {
+ this.appView = appView;
+ this.code = code;
+ this.context = context;
+ }
+
+ @Override
+ public boolean isInstanceFieldMaybeReadBeforeInstruction(
+ Value receiver, DexEncodedField field, Instruction instruction) {
+ if (!code.context().getDefinition().isInstanceInitializer()
+ || receiver.getAliasedValue() != code.getThis()) {
+ return true;
+ }
+ return isFieldMaybeReadBeforeInstructionInInitializer(field, instruction);
+ }
+
+ @Override
+ public boolean isStaticFieldMaybeReadBeforeInstruction(
+ DexEncodedField field, Instruction instruction) {
+ if (!code.context().getDefinition().isClassInitializer()
+ || field.getHolderType() != code.context().getHolderType()) {
+ return true;
+ }
+ return isFieldMaybeReadBeforeInstructionInInitializer(field, instruction);
+ }
+
+ public boolean isFieldMaybeReadBeforeInstructionInInitializer(
+ DexEncodedField field, Instruction instruction) {
+ BasicBlock block = instruction.getBlock();
+
+ // First check if the field may be read in any of the (transitive) predecessor blocks.
+ if (fieldMaybeReadBeforeBlock(field, block)) {
+ return true;
+ }
+
+ // Then check if any of the instructions that precede the given instruction in the current block
+ // may read the field.
+ InstructionIterator instructionIterator = block.iterator();
+ while (instructionIterator.hasNext()) {
+ Instruction current = instructionIterator.next();
+ if (current == instruction) {
+ break;
+ }
+ if (current.readSet(appView, context).contains(field)) {
+ return true;
+ }
+ }
+
+ // Otherwise, the field is not read prior to the given instruction.
+ return false;
+ }
+
+ private boolean fieldMaybeReadBeforeBlock(DexEncodedField field, BasicBlock block) {
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ if (fieldMaybeReadBeforeBlockInclusive(field, predecessor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean fieldMaybeReadBeforeBlockInclusive(DexEncodedField field, BasicBlock block) {
+ return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(field);
+ }
+
+ private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
+ if (fieldsMaybeReadBeforeBlockInclusiveCache == null) {
+ fieldsMaybeReadBeforeBlockInclusiveCache = createFieldsMaybeReadBeforeBlockInclusive();
+ }
+ return fieldsMaybeReadBeforeBlockInclusiveCache;
+ }
+
+ /**
+ * Eagerly creates a mapping from each block to the set of fields that may be read in that block
+ * and its transitive predecessors.
+ */
+ private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() {
+ Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>();
+ Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock());
+ while (!worklist.isEmpty()) {
+ BasicBlock block = worklist.removeFirst();
+ boolean seenBefore = result.containsKey(block);
+ AbstractFieldSet readSet =
+ result.computeIfAbsent(block, ignore -> EmptyFieldSet.getInstance());
+ if (readSet.isTop()) {
+ // We already have unknown information for this block.
+ continue;
+ }
+
+ assert readSet.isKnownFieldSet();
+ KnownFieldSet knownReadSet = readSet.asKnownFieldSet();
+ int oldSize = seenBefore ? knownReadSet.size() : -1;
+
+ // Everything that is read in the predecessor blocks should also be included in the read set
+ // for the current block, so here we join the information from the predecessor blocks into the
+ // current read set.
+ boolean blockOrPredecessorMaybeReadAnyField = false;
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ AbstractFieldSet predecessorReadSet =
+ result.getOrDefault(predecessor, EmptyFieldSet.getInstance());
+ if (predecessorReadSet.isBottom()) {
+ continue;
+ }
+ if (predecessorReadSet.isTop()) {
+ blockOrPredecessorMaybeReadAnyField = true;
+ break;
+ }
+ assert predecessorReadSet.isConcreteFieldSet();
+ if (!knownReadSet.isConcreteFieldSet()) {
+ knownReadSet = new ConcreteMutableFieldSet();
+ }
+ knownReadSet.asConcreteFieldSet().addAll(predecessorReadSet.asConcreteFieldSet());
+ }
+
+ if (!blockOrPredecessorMaybeReadAnyField) {
+ // Finally, we update the read set with the fields that are read by the instructions in the
+ // current block. This can be skipped if the block has already been processed.
+ if (seenBefore) {
+ assert verifyFieldSetContainsAllFieldReadsInBlock(knownReadSet, block, context);
+ } else {
+ for (Instruction instruction : block.getInstructions()) {
+ AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+ if (instructionReadSet.isBottom()) {
+ continue;
+ }
+ if (instructionReadSet.isTop()) {
+ blockOrPredecessorMaybeReadAnyField = true;
+ break;
+ }
+ if (!knownReadSet.isConcreteFieldSet()) {
+ knownReadSet = new ConcreteMutableFieldSet();
+ }
+ knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
+ }
+ }
+ }
+
+ boolean changed = false;
+ if (blockOrPredecessorMaybeReadAnyField) {
+ // Record that this block reads all fields.
+ result.put(block, UnknownFieldSet.getInstance());
+ changed = true;
+ } else {
+ if (knownReadSet != readSet) {
+ result.put(block, knownReadSet.asConcreteFieldSet());
+ }
+ if (knownReadSet.size() != oldSize) {
+ assert knownReadSet.size() > oldSize;
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ // Rerun the analysis for all successors because the state of the current block changed.
+ worklist.addAll(block.getSuccessors());
+ }
+ }
+ return result;
+ }
+
+ private boolean verifyFieldSetContainsAllFieldReadsInBlock(
+ KnownFieldSet readSet, BasicBlock block, ProgramMethod context) {
+ for (Instruction instruction : block.getInstructions()) {
+ AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+ assert !instructionReadSet.isTop();
+ if (instructionReadSet.isBottom()) {
+ continue;
+ }
+ for (DexEncodedField field : instructionReadSet.asConcreteFieldSet().getFields()) {
+ assert readSet.contains(field);
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/TrivialFieldReadBeforeWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/TrivialFieldReadBeforeWriteAnalysis.java
new file mode 100644
index 0000000..9cd76dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/TrivialFieldReadBeforeWriteAnalysis.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2023, 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.fieldaccess.readbeforewrite;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+
+class TrivialFieldReadBeforeWriteAnalysis extends FieldReadBeforeWriteAnalysis {
+
+ @Override
+ public boolean isInstanceFieldMaybeReadBeforeInstruction(
+ Value receiver, DexEncodedField field, Instruction instruction) {
+ return true;
+ }
+
+ @Override
+ public boolean isStaticFieldMaybeReadBeforeInstruction(
+ DexEncodedField field, Instruction instruction) {
+ return true;
+ }
+}
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
index b18d02a..854d046 100644
--- 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
@@ -39,7 +39,7 @@
return this;
}
- Set<DexEncodedField> getFields() {
+ public Set<DexEncodedField> getFields() {
if (InternalOptions.assertionsEnabled()) {
return Collections.unmodifiableSet(fields);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 9c09ef2..50c075e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -10,13 +10,14 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.DominatorTree.Assumption;
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.InvokeDirect;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
@@ -24,14 +25,11 @@
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.ir.optimize.info.field.UnknownInstanceFieldInitializationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.ListUtils;
import java.util.ArrayList;
-import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
public abstract class FieldValueAnalysis {
@@ -53,16 +51,20 @@
final OptimizationFeedback feedback;
private DominatorTree dominatorTree;
- private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
+ private FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis;
final Map<DexEncodedField, List<FieldInitializationInfo>> putsPerField = new IdentityHashMap<>();
FieldValueAnalysis(
- AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
+ AppView<AppInfoWithLiveness> appView,
+ IRCode code,
+ OptimizationFeedback feedback,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
this.appView = appView;
this.code = code;
- this.feedback = feedback;
this.context = code.context();
+ this.feedback = feedback;
+ this.fieldReadBeforeWriteAnalysis = fieldReadBeforeWriteAnalysis;
}
DominatorTree getOrCreateDominatorTree() {
@@ -72,13 +74,6 @@
return dominatorTree;
}
- private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
- if (fieldsMaybeReadBeforeBlockInclusiveCache == null) {
- fieldsMaybeReadBeforeBlockInclusiveCache = createFieldsMaybeReadBeforeBlockInclusive();
- }
- return fieldsMaybeReadBeforeBlockInclusiveCache;
- }
-
boolean isInstanceFieldValueAnalysis() {
return false;
}
@@ -95,9 +90,9 @@
return null;
}
- abstract boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field);
+ abstract boolean isSubjectToOptimizationIgnoringPinning(ProgramField field);
- abstract boolean isSubjectToOptimization(DexEncodedField field);
+ abstract boolean isSubjectToOptimization(ProgramField field);
void recordFieldPut(DexEncodedField field, Instruction instruction) {
recordFieldPut(field, instruction, UnknownInstanceFieldInitializationInfo.getInstance());
@@ -127,13 +122,12 @@
DexField field = fieldPut.getField();
ProgramField programField = appInfo.resolveField(field).getProgramField();
if (programField != null) {
- DexEncodedField encodedField = programField.getDefinition();
- if (isSubjectToOptimization(encodedField)) {
- recordFieldPut(encodedField, fieldPut);
+ if (isSubjectToOptimization(programField)) {
+ recordFieldPut(programField.getDefinition(), fieldPut);
} else if (isStaticFieldValueAnalysis()
&& programField.getHolder().isEnum()
- && isSubjectToOptimizationIgnoringPinning(encodedField)) {
- recordFieldPut(encodedField, fieldPut);
+ && isSubjectToOptimizationIgnoringPinning(programField)) {
+ recordFieldPut(programField.getDefinition(), fieldPut);
}
}
} else if (isInstanceFieldValueAnalysis()
@@ -144,186 +138,54 @@
}
}
+ boolean checkDominance = !isStraightLineCode;
List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
- for (Entry<DexEncodedField, List<FieldInitializationInfo>> entry : putsPerField.entrySet()) {
- DexEncodedField field = entry.getKey();
- List<FieldInitializationInfo> fieldPuts = entry.getValue();
- if (fieldPuts.size() > 1) {
- continue;
- }
- FieldInitializationInfo info = ListUtils.first(fieldPuts);
- Instruction instruction = info.instruction;
- if (instruction.isInvokeDirect()) {
- asInstanceFieldValueAnalysis()
- .recordInstanceFieldIsInitializedWithInfo(field, info.instanceFieldInitializationInfo);
- continue;
- }
- FieldInstruction fieldPut = instruction.asFieldInstruction();
- if (!isStraightLineCode) {
- if (!getOrCreateDominatorTree().dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
- continue;
- }
- }
- boolean priorReadsWillReadSameValue =
- !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
- if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(field, fieldPut)) {
- // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
- if (isStaticFieldValueAnalysis()) {
- // At this point the value read in the field can be only the default static value, if read
- // prior to the put, or the value put, if read after the put. We still want to record it
- // because the default static value is typically null/0, so code present after a null/0
- // check can take advantage of the optimization.
- DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
- asStaticFieldValueAnalysis()
- .updateFieldOptimizationInfoWith2Values(field, fieldPut.value(), valueBeforePut);
- }
- continue;
- }
- updateFieldOptimizationInfo(field, fieldPut, fieldPut.value());
- }
- }
-
- private boolean fieldMaybeReadBeforeInstruction(
- DexEncodedField encodedField, Instruction instruction) {
- BasicBlock block = instruction.getBlock();
-
- // First check if the field may be read in any of the (transitive) predecessor blocks.
- if (fieldMaybeReadBeforeBlock(encodedField, block)) {
- return true;
- }
-
- // Then check if any of the instructions that precede the given instruction in the current block
- // may read the field.
- InstructionIterator instructionIterator = block.iterator();
- while (instructionIterator.hasNext()) {
- Instruction current = instructionIterator.next();
- if (current == instruction) {
- break;
- }
- if (current.readSet(appView, context).contains(encodedField)) {
- return true;
- }
- }
-
- // Otherwise, the field is not read prior to the given instruction.
- return false;
- }
-
- private boolean fieldMaybeReadBeforeBlock(DexEncodedField encodedField, BasicBlock block) {
- for (BasicBlock predecessor : block.getPredecessors()) {
- if (fieldMaybeReadBeforeBlockInclusive(encodedField, predecessor)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean fieldMaybeReadBeforeBlockInclusive(
- DexEncodedField encodedField, BasicBlock block) {
- return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(encodedField);
- }
-
- /**
- * Eagerly creates a mapping from each block to the set of fields that may be read in that block
- * and its transitive predecessors.
- */
- private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() {
- Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>();
- Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock());
- while (!worklist.isEmpty()) {
- BasicBlock block = worklist.removeFirst();
- boolean seenBefore = result.containsKey(block);
- AbstractFieldSet readSet =
- result.computeIfAbsent(block, ignore -> EmptyFieldSet.getInstance());
- if (readSet.isTop()) {
- // We already have unknown information for this block.
- continue;
- }
-
- assert readSet.isKnownFieldSet();
- KnownFieldSet knownReadSet = readSet.asKnownFieldSet();
- int oldSize = seenBefore ? knownReadSet.size() : -1;
-
- // Everything that is read in the predecessor blocks should also be included in the read set
- // for the current block, so here we join the information from the predecessor blocks into the
- // current read set.
- boolean blockOrPredecessorMaybeReadAnyField = false;
- for (BasicBlock predecessor : block.getPredecessors()) {
- AbstractFieldSet predecessorReadSet =
- result.getOrDefault(predecessor, EmptyFieldSet.getInstance());
- if (predecessorReadSet.isBottom()) {
- continue;
- }
- if (predecessorReadSet.isTop()) {
- blockOrPredecessorMaybeReadAnyField = true;
- break;
- }
- assert predecessorReadSet.isConcreteFieldSet();
- if (!knownReadSet.isConcreteFieldSet()) {
- knownReadSet = new ConcreteMutableFieldSet();
- }
- knownReadSet.asConcreteFieldSet().addAll(predecessorReadSet.asConcreteFieldSet());
- }
-
- if (!blockOrPredecessorMaybeReadAnyField) {
- // Finally, we update the read set with the fields that are read by the instructions in the
- // current block. This can be skipped if the block has already been processed.
- if (seenBefore) {
- assert verifyFieldSetContainsAllFieldReadsInBlock(knownReadSet, block, context);
- } else {
- for (Instruction instruction : block.getInstructions()) {
- AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
- if (instructionReadSet.isBottom()) {
- continue;
- }
- if (instructionReadSet.isTop()) {
- blockOrPredecessorMaybeReadAnyField = true;
- break;
- }
- if (!knownReadSet.isConcreteFieldSet()) {
- knownReadSet = new ConcreteMutableFieldSet();
- }
- knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
+ putsPerField.forEach(
+ (field, fieldPuts) -> {
+ if (fieldPuts.size() > 1) {
+ return;
}
- }
- }
-
- boolean changed = false;
- if (blockOrPredecessorMaybeReadAnyField) {
- // Record that this block reads all fields.
- result.put(block, UnknownFieldSet.getInstance());
- changed = true;
- } else {
- if (knownReadSet != readSet) {
- result.put(block, knownReadSet.asConcreteFieldSet());
- }
- if (knownReadSet.size() != oldSize) {
- assert knownReadSet.size() > oldSize;
- changed = true;
- }
- }
-
- if (changed) {
- // Rerun the analysis for all successors because the state of the current block changed.
- worklist.addAll(block.getSuccessors());
- }
- }
- return result;
- }
-
- private boolean verifyFieldSetContainsAllFieldReadsInBlock(
- KnownFieldSet readSet, BasicBlock block, ProgramMethod context) {
- for (Instruction instruction : block.getInstructions()) {
- AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
- assert !instructionReadSet.isTop();
- if (instructionReadSet.isBottom()) {
- continue;
- }
- for (DexEncodedField field : instructionReadSet.asConcreteFieldSet().getFields()) {
- assert readSet.contains(field);
- }
- }
- return true;
+ FieldInitializationInfo info = ListUtils.first(fieldPuts);
+ Instruction instruction = info.instruction;
+ if (instruction.isInvokeDirect()) {
+ asInstanceFieldValueAnalysis()
+ .recordInstanceFieldIsInitializedWithInfo(
+ field, info.instanceFieldInitializationInfo);
+ return;
+ }
+ FieldInstruction fieldPut = instruction.asFieldInstruction();
+ if (checkDominance
+ && !getOrCreateDominatorTree()
+ .dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+ return;
+ }
+ boolean priorReadsWillReadSameValue =
+ !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
+ if (!priorReadsWillReadSameValue) {
+ if (fieldPut.isInstancePut()) {
+ InstancePut instancePut = fieldPut.asInstancePut();
+ if (fieldReadBeforeWriteAnalysis.isInstanceFieldMaybeReadBeforeInstruction(
+ instancePut.object(), field, instancePut)) {
+ return;
+ }
+ } else {
+ if (fieldReadBeforeWriteAnalysis.isStaticFieldMaybeReadBeforeInstruction(
+ field, fieldPut)) {
+ // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
+ // At this point the value read in the field can be only the default static value,
+ // if read prior to the put, or the value put, if read after the put. We still want
+ // to record it because the default static value is typically null/0, so code
+ // present after a null/0 check can take advantage of the optimization.
+ DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
+ asStaticFieldValueAnalysis()
+ .updateFieldOptimizationInfoWith2Values(
+ field, fieldPut.value(), valueBeforePut);
+ return;
+ }
+ }
+ }
+ updateFieldOptimizationInfo(field, fieldPut, fieldPut.value());
+ });
}
abstract void updateFieldOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index c4061ec..9ae014f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -11,7 +11,9 @@
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -50,9 +52,10 @@
AppView<AppInfoWithLiveness> appView,
IRCode code,
OptimizationFeedback feedback,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
DexClassAndMethod parentConstructor,
InvokeDirect parentConstructorCall) {
- super(appView, code, feedback);
+ super(appView, code, feedback, fieldReadBeforeWriteAnalysis);
this.factory = appView.instanceFieldInitializationInfoFactory();
this.parentConstructor = parentConstructor;
this.parentConstructorCall = parentConstructorCall;
@@ -67,10 +70,11 @@
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
OptimizationFeedback feedback,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
Timing timing) {
timing.begin("Analyze instance initializer");
InstanceFieldInitializationInfoCollection result =
- run(appView, code, classInitializerDefaultsResult, feedback);
+ run(appView, code, classInitializerDefaultsResult, feedback, fieldReadBeforeWriteAnalysis);
timing.end();
return result;
}
@@ -79,7 +83,8 @@
AppView<?> appView,
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
- OptimizationFeedback feedback) {
+ OptimizationFeedback feedback,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
assert appView.appInfo().hasLiveness();
assert appView.enableWholeProgramOptimizations();
assert code.context().getDefinition().isInstanceInitializer();
@@ -101,6 +106,7 @@
appView.withLiveness(),
code,
feedback,
+ fieldReadBeforeWriteAnalysis,
parentConstructor,
parentConstructorCall);
analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
@@ -119,12 +125,12 @@
}
@Override
- boolean isSubjectToOptimization(DexEncodedField field) {
- return !field.isStatic() && field.getHolderType() == context.getHolderType();
+ boolean isSubjectToOptimization(ProgramField field) {
+ return !field.getAccessFlags().isStatic() && field.getHolderType() == context.getHolderType();
}
@Override
- boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
+ boolean isSubjectToOptimizationIgnoringPinning(ProgramField field) {
throw new Unreachable("Used by static analysis only.");
}
@@ -203,7 +209,7 @@
if (abstractValue.isSingleValue()) {
return abstractValue.asSingleValue();
}
- DexType fieldType = field.type();
+ DexType fieldType = field.getType();
if (fieldType.isClassType()) {
ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
@@ -229,7 +235,7 @@
private boolean fieldNeverWrittenBetweenInstancePutAndMethodExit(
DexEncodedField field, InstancePut instancePut) {
- if (field.isFinal()) {
+ if (field.getAccessFlags().isFinal()) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 4df6b75..5f8381d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -49,8 +51,11 @@
private final Map<Value, AbstractValue> computedValues = new IdentityHashMap<>();
private StaticFieldValueAnalysis(
- AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
- super(appView, code, feedback);
+ AppView<AppInfoWithLiveness> appView,
+ IRCode code,
+ OptimizationFeedback feedback,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
+ super(appView, code, feedback, fieldReadBeforeWriteAnalysis);
builder = StaticFieldValues.builder(code.context().getHolder());
}
@@ -59,13 +64,15 @@
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
OptimizationFeedback feedback,
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
Timing timing) {
assert appView.appInfo().hasLiveness();
assert appView.enableWholeProgramOptimizations();
assert code.context().getDefinition().isClassInitializer();
timing.begin("Analyze class initializer");
StaticFieldValues result =
- new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
+ new StaticFieldValueAnalysis(
+ appView.withLiveness(), code, feedback, fieldReadBeforeWriteAnalysis)
.analyze(classInitializerDefaultsResult);
timing.end();
return result;
@@ -117,19 +124,22 @@
}
@Override
- boolean isSubjectToOptimization(DexEncodedField field) {
- return field.isStatic()
- && field.getHolderType() == context.getHolderType()
- && appView.appInfo().isFieldOnlyWrittenInMethod(field, context.getDefinition());
- }
-
- @Override
- boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
- return field.isStatic()
+ boolean isSubjectToOptimization(ProgramField field) {
+ return field.getAccessFlags().isStatic()
&& field.getHolderType() == context.getHolderType()
&& appView
.appInfo()
- .isFieldOnlyWrittenInMethodIgnoringPinning(field, context.getDefinition());
+ .isFieldOnlyWrittenInMethod(field.getDefinition(), context.getDefinition());
+ }
+
+ @Override
+ boolean isSubjectToOptimizationIgnoringPinning(ProgramField field) {
+ return field.getAccessFlags().isStatic()
+ && field.getHolderType() == context.getHolderType()
+ && appView
+ .appInfo()
+ .isFieldOnlyWrittenInMethodIgnoringPinning(
+ field.getDefinition(), context.getDefinition());
}
@Override
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 fc8d1f2..bc02268 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
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
import com.android.tools.r8.ir.analysis.fieldaccess.FieldAccessAnalysis;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
@@ -979,10 +980,16 @@
timing.end();
}
+ FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis =
+ FieldReadBeforeWriteAnalysis.create(appView, code, method);
if (fieldAccessAnalysis != null) {
timing.begin("Analyze field accesses");
fieldAccessAnalysis.recordFieldAccesses(
- code, bytecodeMetadataProviderBuilder, feedback, methodProcessor);
+ code,
+ bytecodeMetadataProviderBuilder,
+ feedback,
+ fieldReadBeforeWriteAnalysis,
+ methodProcessor);
if (classInitializerDefaultsResult != null) {
fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
}
@@ -999,11 +1006,21 @@
if (method.getDefinition().isClassInitializer()) {
staticFieldValues =
StaticFieldValueAnalysis.run(
- appView, code, classInitializerDefaultsResult, feedback, timing);
+ appView,
+ code,
+ classInitializerDefaultsResult,
+ feedback,
+ fieldReadBeforeWriteAnalysis,
+ timing);
} else {
instanceFieldInitializationInfos =
InstanceFieldValueAnalysis.run(
- appView, code, classInitializerDefaultsResult, feedback, timing);
+ appView,
+ code,
+ classInitializerDefaultsResult,
+ feedback,
+ fieldReadBeforeWriteAnalysis,
+ timing);
}
}
enumUnboxer.recordEnumState(method.getHolder(), staticFieldValues);
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 f8e784e..c92342f 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
@@ -420,9 +420,10 @@
}
builder.setParent(invokedMethod);
} else {
- builder
- .markAllFieldsAsRead()
- .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ builder.markAllFieldsAsRead();
+ if (invoke.instructionMayHaveSideEffects(appView, context)) {
+ builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ }
for (Value inValue : invoke.inValues()) {
if (couldBeReceiverValue(inValue, receiver, aliasesThroughAssumeAndCheckCasts)) {
builder.setReceiverMayEscapeOutsideConstructorChain();
@@ -453,9 +454,10 @@
case INVOKE_VIRTUAL:
{
InvokeMethod invoke = instruction.asInvokeMethod();
- builder
- .markAllFieldsAsRead()
- .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ builder.markAllFieldsAsRead();
+ if (invoke.instructionMayHaveSideEffects(appView, context)) {
+ builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ }
for (Value argument : invoke.arguments()) {
if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
builder.setReceiverMayEscapeOutsideConstructorChain();
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 39d8035..07a5bb2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -954,14 +954,6 @@
return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
}
- public boolean mayPropagateArgumentsTo(ProgramMethod method) {
- DexMethod reference = method.getReference();
- return method.getDefinition().hasCode()
- && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
- && !neverReprocess.contains(reference)
- && !keepInfo.getMethodInfo(method).isPinned(options());
- }
-
public boolean mayPropagateValueFor(
AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) {
assert checkIfObsolete();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 2cdeffd..bb382f8 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
@@ -96,7 +97,11 @@
method -> {
IRCode code = method.buildIR(appView);
fieldAccessAnalysis.recordFieldAccesses(
- code, BytecodeMetadataProvider.builder(), feedback, new PrimaryMethodProcessorMock());
+ code,
+ BytecodeMetadataProvider.builder(),
+ feedback,
+ FieldReadBeforeWriteAnalysis.trivial(),
+ new PrimaryMethodProcessorMock());
});
int bitsReadInBitField = feedback.bitsReadPerField.getInt(uniqueFieldByName(clazz, "bitField"));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullInstanceFieldPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullInstanceFieldPropagationTest.java
new file mode 100644
index 0000000..972cc79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullInstanceFieldPropagationTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, 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.membervaluepropagation;
+
+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.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 NonNullInstanceFieldPropagationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+ assertEquals(1, inspector.allClasses().size());
+ assertEquals(1, mainClassSubject.allMethods().size());
+ assertEquals(0, mainClassSubject.allFields().size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Main p = new Main();
+ p.mThreadChecker.assertOnValidThread();
+ }
+
+ public final ThreadChecker mThreadChecker = new ThreadChecker();
+ }
+
+ static class ThreadChecker {
+ public void assertOnValidThread() {
+ if (System.currentTimeMillis() == 0) {
+ throw new RuntimeException();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 92404f0..8078ed6 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -101,6 +101,7 @@
" void <init>();",
"}",
"-neverclassinline class *",
+ "-nohorizontalclassmerging class adaptresourcefilenames.B",
"-nohorizontalclassmerging class adaptresourcefilenames.pkg.C",
"-nohorizontalclassmerging class adaptresourcefilenames.pkg.innerpkg.D");
}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java b/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java
index 31d557e..babd7a7 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java
@@ -59,7 +59,10 @@
Bar value = new Bar();
public static void main(String[] args) {
- new Foo();
+ Foo foo = new Foo();
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(foo);
+ }
}
}
}