blob: 248e04bcabf74ab817502123c0ffbcf3fa2b1003 [file] [log] [blame]
// Copyright (c) 2017, 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.type;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Assume;
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.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.AssumeRemover;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.function.Consumer;
public class TypeAnalysis {
private enum Mode {
UNSET,
WIDENING, // initial analysis, including fixed-point iteration for phis and updating with less
// specific info, e.g., removing assume nodes.
NARROWING, // updating with more specific info, e.g., passing the return value of the inlinee.
PROPAGATE, // effectively NARROWING_OR_WIDENING
NO_CHANGE // utility to ensure types are up to date
}
private final boolean mayHaveImpreciseTypes;
private boolean keepRedundantBlocksAfterAssumeRemoval = false;
private Mode mode = Mode.UNSET;
private final AppView<?> appView;
private final AssumeRemover assumeRemover;
private final IRCode code;
private final WorkList<Value> worklist = WorkList.newIdentityWorkList();
public TypeAnalysis(AppView<?> appView, IRCode code) {
this(appView, code, false);
}
public TypeAnalysis(AppView<?> appView, IRCode code, boolean mayHaveImpreciseTypes) {
this.appView = appView;
this.assumeRemover = new AssumeRemover(appView, code);
this.code = code;
this.mayHaveImpreciseTypes = mayHaveImpreciseTypes;
}
// Allows disabling the removal of redundant blocks after assume removal.
public TypeAnalysis setKeepRedundantBlocksAfterAssumeRemoval(
boolean keepRedundantBlocksAfterAssumeRemoval) {
this.keepRedundantBlocksAfterAssumeRemoval = keepRedundantBlocksAfterAssumeRemoval;
return this;
}
private void analyze() {
while (worklist.hasNext()) {
analyzeValue(worklist.removeSeen());
}
}
public void widening() {
mode = Mode.WIDENING;
assert verifyIsEmpty();
code.topologicallySortedBlocks().forEach(this::analyzeBasicBlock);
analyze();
}
public void widening(Iterable<Value> values) {
analyzeValues(values, Mode.WIDENING);
}
public void narrowing() {
mode = Mode.NARROWING;
assert verifyIsEmpty();
code.topologicallySortedBlocks().forEach(this::analyzeBasicBlock);
analyze();
}
public void narrowing(Iterable<? extends Value> values) {
analyzeValues(values, Mode.NARROWING);
}
public void narrowingWithAssumeRemoval(Iterable<? extends Value> values) {
narrowingWithAssumeRemoval(values, ConsumerUtils.emptyConsumer());
}
public void narrowingWithAssumeRemoval(
Iterable<? extends Value> values, Consumer<Assume> redundantAssumeConsumer) {
narrowing(values);
removeRedundantAssumeInstructions(redundantAssumeConsumer);
}
public void propagate(Iterable<? extends Value> values) {
analyzeValues(values, Mode.PROPAGATE);
}
public void propagateWithAssumeRemoval(Iterable<? extends Value> values) {
propagateWithAssumeRemoval(values, ConsumerUtils.emptyConsumer());
}
public void propagateWithAssumeRemoval(
Iterable<? extends Value> values, Consumer<Assume> redundantAssumeConsumer) {
propagate(values);
removeRedundantAssumeInstructions(redundantAssumeConsumer);
}
private void removeRedundantAssumeInstructions(Consumer<Assume> redundantAssumeConsumer) {
Set<Value> affectedValuesFromAssumeRemoval = Sets.newIdentityHashSet();
while (assumeRemover.removeRedundantAssumeInstructions(
affectedValuesFromAssumeRemoval, redundantAssumeConsumer)) {
widening(affectedValuesFromAssumeRemoval);
AffectedValues affectedValuesFromPhiRemoval = new AffectedValues();
code.removeAllDeadAndTrivialPhis(affectedValuesFromPhiRemoval);
narrowing(affectedValuesFromPhiRemoval);
affectedValuesFromAssumeRemoval.clear();
}
if (!keepRedundantBlocksAfterAssumeRemoval) {
code.removeRedundantBlocks();
}
}
public boolean verifyIsEmpty() {
assert !assumeRemover.hasAffectedAssumeInstructions();
assert worklist.isEmpty();
return true;
}
public static void verifyValuesUpToDate(AppView<?> appView, IRCode code) {
TypeAnalysis typeAnalysis = new TypeAnalysis(appView, code);
typeAnalysis.mode = Mode.NO_CHANGE;
code.topologicallySortedBlocks().forEach(typeAnalysis::analyzeBasicBlock);
typeAnalysis.analyze();
}
public static boolean verifyValuesUpToDate(
AppView<?> appView, IRCode code, Iterable<? extends Value> values) {
new TypeAnalysis(appView, code).analyzeValues(values, Mode.NO_CHANGE);
return true;
}
private void analyzeValues(Iterable<? extends Value> values, Mode mode) {
this.mode = mode;
assert worklist.isEmpty();
values.forEach(this::enqueue);
analyze();
}
private void enqueue(Value v) {
worklist.addFirstIfNotSeen(v);
}
private void analyzeBasicBlock(BasicBlock block) {
for (Instruction instruction : block.getInstructions()) {
Value outValue = instruction.outValue();
if (outValue == null) {
continue;
}
if (instruction.isArgument()) {
// The type for Argument, a quasi instruction is already set correctly during IR building.
// Note that we don't need to enqueue the out value of arguments here because it's constant.
} else if (instruction.hasInvariantOutType()) {
TypeElement derived = instruction.evaluate(appView);
updateTypeOfValue(outValue, derived);
} else {
enqueue(outValue);
}
}
for (Phi phi : block.getPhis()) {
enqueue(phi);
}
}
private void analyzeValue(Value value) {
TypeElement previous = value.getType();
TypeElement derived =
value.isPhi() ? value.asPhi().computePhiType(appView) : value.definition.evaluate(appView);
assert mayHaveImpreciseTypes || derived.isPreciseType();
assert !previous.isPreciseType() || derived.isPreciseType();
updateTypeOfValue(value, derived);
}
private void updateTypeOfValue(Value value, TypeElement type) {
assert mode != Mode.UNSET;
if (value.isDefinedByInstructionSatisfying(Instruction::isAssume)) {
assumeRemover.addAffectedAssumeInstruction(value.getDefinition().asAssume());
}
TypeElement current = value.getType();
if (current.equals(type)) {
return;
}
assert mode != Mode.NO_CHANGE
: "Unexpected type change for value "
+ value
+ " defined by "
+ (value.isPhi() ? "phi" : value.getDefinition())
+ ": was "
+ type
+ ", but expected "
+ current
+ " (context: "
+ code.context()
+ ")";
if (type.isBottom()) {
return;
}
switch (mode) {
case NARROWING:
value.narrowing(appView, code.context(), type);
break;
case PROPAGATE:
value.setType(type);
break;
case WIDENING:
value.widening(appView, type);
break;
default:
throw new Unreachable();
}
// propagate the type change to (instruction) users if any.
for (Instruction instruction : value.uniqueUsers()) {
Value outValue = instruction.outValue();
if (outValue != null) {
enqueue(outValue);
}
}
// Propagate the type change to phi users if any.
for (Phi phi : value.uniquePhiUsers()) {
enqueue(phi);
}
}
public static DexType getRefinedReceiverType(
AppView<AppInfoWithLiveness> appView, InvokeMethodWithReceiver invoke) {
return toRefinedReceiverType(
invoke.getReceiver().getDynamicType(appView), invoke.getInvokedMethod(), appView);
}
@SuppressWarnings("ReferenceEquality")
public static DexType toRefinedReceiverType(
DynamicType dynamicReceiverType,
DexMethod method,
AppView<? extends AppInfoWithClassHierarchy> appView) {
DexType staticReceiverType = method.getHolderType();
TypeElement staticReceiverTypeElement = staticReceiverType.toTypeElement(appView);
TypeElement dynamicReceiverUpperBoundType =
dynamicReceiverType.getDynamicUpperBoundType(staticReceiverTypeElement);
if (dynamicReceiverUpperBoundType.isClassType()) {
ClassTypeElement dynamicReceiverUpperBoundClassType =
dynamicReceiverUpperBoundType.asClassType();
DexType refinedType = dynamicReceiverUpperBoundClassType.toDexType(appView.dexItemFactory());
if (appView.appInfo().isSubtype(refinedType, staticReceiverType)) {
return refinedType;
}
}
return staticReceiverType;
}
}