blob: f11d945c478d22eca13e988b39d0cfd4268fa09a [file] [log] [blame]
// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation.propagation;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.ProgramField;
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.DominatorTree;
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.StaticPut;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.LazyBox;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
class FieldReadBeforeWriteAnalysis {
private final AppView<AppInfoWithLiveness> appView;
private final IRCode code;
private final ProgramMethod context;
private final LazyBox<DominatorTree> lazyDominatorTree;
private final List<BasicBlock> returnBlocks;
private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
public FieldReadBeforeWriteAnalysis(AppView<AppInfoWithLiveness> appView, IRCode code) {
this.appView = appView;
this.code = code;
this.context = code.context();
this.lazyDominatorTree = new LazyBox<>(() -> new DominatorTree(code));
this.returnBlocks = code.computeNormalExitBlocks();
}
public boolean isInstanceFieldNeverReadBeforeWrite(ProgramField field) {
assert field.getHolder() == context.getHolder();
InstancePut instancePut = null;
for (InstancePut candidate :
code.getThis().<InstancePut>uniqueUsers(Instruction::isInstancePut)) {
if (candidate.getField().isIdenticalTo(field.getReference())) {
if (instancePut != null) {
// In the somewhat unusual case (?) that the same constructor assigns the same field
// multiple times, we simply bail out and conservatively report that the field is maybe
// read before it is written.
return false;
}
instancePut = candidate;
}
}
// TODO(b/296030319): Improve precision using escape analysis for receiver.
return instancePut != null
&& !isFieldMaybeReadBeforeInstructionInInitializer(field, instancePut)
&& lazyDominatorTree.computeIfAbsent().dominatesAllOf(instancePut.getBlock(), returnBlocks);
}
public boolean isStaticFieldNeverReadBeforeWrite(ProgramField field) {
assert field.getHolder() == context.getHolder();
StaticPut staticPut = null;
for (StaticPut candidate : code.<StaticPut>instructions(Instruction::isStaticPut)) {
if (candidate.getField().isIdenticalTo(field.getReference())) {
if (staticPut != null) {
// In the somewhat unusual case (?) that the same constructor assigns the same field
// multiple times, we simply bail out and conservatively report that the field is maybe
// read before it is written.
return false;
}
staticPut = candidate;
}
}
return staticPut != null
&& !isFieldMaybeReadBeforeInstructionInInitializer(field, staticPut)
&& lazyDominatorTree.computeIfAbsent().dominatesAllOf(staticPut.getBlock(), returnBlocks);
}
private boolean isFieldMaybeReadBeforeInstructionInInitializer(
DexClassAndField 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(DexClassAndField field, BasicBlock block) {
for (BasicBlock predecessor : block.getPredecessors()) {
if (fieldMaybeReadBeforeBlockInclusive(field, predecessor)) {
return true;
}
}
return false;
}
private boolean fieldMaybeReadBeforeBlockInclusive(DexClassAndField 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;
}
}