blob: 8c5e7cc1f4941db0c42cf58a5d5b0a497ec93c52 [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.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Argument;
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.Phi;
import com.android.tools.r8.ir.code.Value;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
public class TypeAnalysis {
private final AppInfoWithSubtyping appInfo;
private final DexEncodedMethod encodedMethod;
private final IRCode code;
private final Deque<BasicBlock> worklist = new ArrayDeque<>();
private final Map<Value, TypeLatticeElement> typeMap = Maps.newHashMap();
private final Map<Value, Set<BasicBlock>> users = Maps.newHashMap();
public TypeAnalysis(AppInfoWithSubtyping appInfo, DexEncodedMethod encodedMethod, IRCode code) {
this.appInfo = appInfo;
this.encodedMethod = encodedMethod;
this.code = code;
}
public void run() {
worklist.addAll(code.topologicallySortedBlocks());
while (!worklist.isEmpty()) {
processBasicBlock(worklist.poll());
}
}
private void addToWorklist(BasicBlock block) {
if (!worklist.contains(block)) {
worklist.add(block);
}
}
private void processBasicBlock(BasicBlock block) {
int argumentsSeen = encodedMethod.accessFlags.isStatic() ? 0 : -1;
for (Instruction instruction : block.getInstructions()) {
TypeLatticeElement derived = Bottom.getInstance();
Value outValue = instruction.outValue();
// Argument, a quasi instruction, needs to be handled specially:
// 1) We can derive its type from the method signature; and
// 2) It does not have an out value, so we can skip the env updating.
if (instruction instanceof Argument) {
if (argumentsSeen < 0) {
// Receiver
derived = TypeLatticeElement.fromDexType(encodedMethod.method.holder, false);
} else {
DexType argType = encodedMethod.method.proto.parameters.values[argumentsSeen];
derived = TypeLatticeElement.fromDexType(argType, true);
}
argumentsSeen++;
} else {
// Register this block as a user for in values to revisit this if in values are updated.
instruction.inValues()
.forEach(v -> registerAsUserOfValue(v, block, Sets.newIdentityHashSet()));
if (outValue != null) {
derived = instruction.evaluate(appInfo, this::getLatticeElement);
}
}
if (outValue != null) {
TypeLatticeElement current = getLatticeElement(outValue);
if (!current.equals(derived)) {
updateTypeOfValue(outValue, derived);
}
}
}
}
private void registerAsUserOfValue(Value value, BasicBlock block, Set<Value> seenPhis) {
if (value.isPhi() && seenPhis.add(value)) {
for (Value operand : value.asPhi().getOperands()) {
registerAsUserOfValue(operand, block, seenPhis);
}
} else {
users.computeIfAbsent(value, k -> Sets.newIdentityHashSet()).add(block);
}
}
private void updateTypeOfValue(Value value, TypeLatticeElement type) {
setLatticeElement(value, type);
// Revisit the blocks that use the value whose type binding has just been updated.
users.getOrDefault(value, Collections.emptySet()).forEach(this::addToWorklist);
// Propagate the type change to phi users if any.
for (Phi phi : value.uniquePhiUsers()) {
TypeLatticeElement phiType = computePhiType(phi);
if (!getLatticeElement(phi).equals(phiType)) {
updateTypeOfValue(phi, phiType);
}
}
}
private TypeLatticeElement computePhiType(Phi phi) {
// Type of phi(v1, v2, ..., vn) is the least upper bound of all those n operands.
BinaryOperator<TypeLatticeElement> joiner = TypeLatticeElement.joiner(appInfo);
return phi.getOperands().stream().reduce(
Bottom.getInstance(),
(acc, value) -> joiner.apply(acc, getLatticeElement(value)),
joiner::apply);
}
private void setLatticeElement(Value value, TypeLatticeElement type) {
typeMap.put(value, type);
}
TypeLatticeElement getLatticeElement(Value value) {
return typeMap.getOrDefault(value, Bottom.getInstance());
}
@VisibleForTesting
void forEach(BiConsumer<Value, TypeLatticeElement> consumer) {
typeMap.forEach(consumer);
}
}