blob: b63dbb200aa850bf109deca9555a2ea877b6e3bd [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.AppInfo;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.Value;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
/**
* The base abstraction of lattice elements for local type analysis.
*/
public abstract class TypeLatticeElement {
public static final BottomTypeLatticeElement BOTTOM = BottomTypeLatticeElement.getInstance();
public static final TopTypeLatticeElement TOP = TopTypeLatticeElement.getInstance();
public static final IntTypeLatticeElement INT = IntTypeLatticeElement.getInstance();
public static final FloatTypeLatticeElement FLOAT = FloatTypeLatticeElement.getInstance();
public static final SingleTypeLatticeElement SINGLE = SingleTypeLatticeElement.getInstance();
public static final LongTypeLatticeElement LONG = LongTypeLatticeElement.getInstance();
public static final DoubleTypeLatticeElement DOUBLE = DoubleTypeLatticeElement.getInstance();
public static final WideTypeLatticeElement WIDE = WideTypeLatticeElement.getInstance();
public static final ReferenceTypeLatticeElement NULL =
ReferenceTypeLatticeElement.getNullTypeLatticeElement();
public static final ReferenceTypeLatticeElement REFERENCE =
ReferenceTypeLatticeElement.getReferenceTypeLatticeElement();
// TODO(b/72693244): Switch to NullLatticeElement.
private final boolean isNullable;
TypeLatticeElement(boolean isNullable) {
this.isNullable = isNullable;
}
public boolean isNullable() {
return isNullable;
}
public NullLatticeElement nullElement() {
if (isNull()) {
return NullLatticeElement.definitelyNull();
}
if (!isNullable()) {
return NullLatticeElement.definitelyNotNull();
}
return NullLatticeElement.maybeNull();
}
/**
* Defines how to join with null or switch to nullable lattice element.
*
* @return {@link TypeLatticeElement} a result of joining with null.
*/
public abstract TypeLatticeElement asNullable();
/**
* Defines how to switch to non-nullable lattice element.
*
* @return {@link TypeLatticeElement} a similar lattice element with nullable flag flipped.
*/
public TypeLatticeElement asNonNullable() {
return BOTTOM;
}
String isNullableString() {
return isNullable() ? "" : "@NonNull ";
}
/**
* Computes the least upper bound of the current and the other elements.
*
* @param other {@link TypeLatticeElement} to join.
* @param appInfo {@link AppInfo}.
* @return {@link TypeLatticeElement}, a least upper bound of {@param this} and {@param other}.
*/
public TypeLatticeElement join(TypeLatticeElement other, AppInfo appInfo) {
if (isBottom()) {
return other;
}
if (other.isBottom()) {
return this;
}
if (isTop() || other.isTop()) {
return TOP;
}
if (isNull()) {
return other.asNullable();
}
if (other.isNull()) {
return asNullable();
}
if (isPrimitive()) {
return other.isPrimitive()
? PrimitiveTypeLatticeElement.join(
asPrimitiveTypeLatticeElement(), other.asPrimitiveTypeLatticeElement())
: TOP;
}
if (other.isPrimitive()) {
// By the above case, !(isPrimitive())
return TOP;
}
// From now on, this and other are reference types, but might be imprecise yet.
assert isReference() && other.isReference();
if (!isPreciseType() || !other.isPreciseType()) {
if (isReferenceInstance()) {
return this;
}
assert other.isReferenceInstance();
return other;
}
// From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
boolean isNullable = isNullable() || other.isNullable();
if (getClass() != other.getClass()) {
return objectClassType(appInfo, isNullable);
}
// From now on, getClass() == other.getClass()
if (isArrayType()) {
assert other.isArrayType();
ArrayTypeLatticeElement a1 = asArrayTypeLatticeElement();
ArrayTypeLatticeElement a2 = other.asArrayTypeLatticeElement();
// Identical types are the same elements
if (a1.getArrayType() == a2.getArrayType()) {
return a1.isNullable() ? a1 : a2;
}
// If non-equal, find the inner-most reference types for each.
DexType a1BaseReferenceType = a1.getArrayBaseType(appInfo.dexItemFactory);
int a1Nesting = a1.getNesting();
if (a1BaseReferenceType.isPrimitiveType()) {
a1Nesting--;
a1BaseReferenceType = appInfo.dexItemFactory.objectType;
}
DexType a2BaseReferenceType = a2.getArrayBaseType(appInfo.dexItemFactory);
int a2Nesting = a2.getNesting();
if (a2BaseReferenceType.isPrimitiveType()) {
a2Nesting--;
a2BaseReferenceType = appInfo.dexItemFactory.objectType;
}
assert a1BaseReferenceType.isClassType() && a2BaseReferenceType.isClassType();
// If any nestings hit zero object is the join.
if (a1Nesting == 0 || a2Nesting == 0) {
return objectClassType(appInfo, isNullable);
}
// If the nestings differ the join is the smallest nesting level.
if (a1Nesting != a2Nesting) {
int min = Math.min(a1Nesting, a2Nesting);
return objectArrayType(appInfo, min, isNullable);
}
// For different class element types, compute the least upper bound of element types.
DexType lub =
a1BaseReferenceType.computeLeastUpperBoundOfClasses(appInfo, a2BaseReferenceType);
// Create the full array type.
DexType arrayTypeLub = appInfo.dexItemFactory.createArrayType(a1Nesting, lub);
return new ArrayTypeLatticeElement(arrayTypeLub, isNullable);
}
if (isClassType()) {
assert other.isClassType();
ClassTypeLatticeElement c1 = asClassTypeLatticeElement();
ClassTypeLatticeElement c2 = other.asClassTypeLatticeElement();
DexType lubType =
c1.getClassType().computeLeastUpperBoundOfClasses(appInfo, c2.getClassType());
return new ClassTypeLatticeElement(lubType, isNullable,
computeLeastUpperBoundOfInterfaces(appInfo, c1.getInterfaces(), c2.getInterfaces()));
}
throw new Unreachable("unless a new type lattice is introduced.");
}
private enum InterfaceMarker {
LEFT,
RIGHT
}
private static class InterfaceWithMarker {
final DexType itf;
final InterfaceMarker marker;
InterfaceWithMarker(DexType itf, InterfaceMarker marker) {
this.itf = itf;
this.marker = marker;
}
}
static Set<DexType> computeLeastUpperBoundOfInterfaces(
AppInfo appInfo, Set<DexType> s1, Set<DexType> s2) {
Map<DexType, Set<InterfaceMarker>> seen = new IdentityHashMap<>();
Queue<InterfaceWithMarker> worklist = new ArrayDeque<>();
for (DexType itf1 : s1) {
worklist.add(new InterfaceWithMarker(itf1, InterfaceMarker.LEFT));
}
for (DexType itf2 : s2) {
worklist.add(new InterfaceWithMarker(itf2, InterfaceMarker.RIGHT));
}
while (!worklist.isEmpty()) {
InterfaceWithMarker item = worklist.poll();
DexType itf = item.itf;
InterfaceMarker marker = item.marker;
Set<InterfaceMarker> markers = seen.computeIfAbsent(itf, k -> new HashSet<>());
// If this interface is a lower one in this set, skip.
if (markers.contains(marker)) {
continue;
}
// If this interface is already visited by the other set, add marker for this set and skip.
if (markers.size() == 1) {
markers.add(marker);
continue;
}
// Otherwise, this type is freshly visited.
markers.add(marker);
// Put super interfaces into the worklist.
DexClass itfClass = appInfo.definitionFor(itf);
if (itfClass != null) {
for (DexType superItf : itfClass.interfaces.values) {
markers = seen.computeIfAbsent(superItf, k -> new HashSet<>());
if (!markers.contains(marker)) {
worklist.add(new InterfaceWithMarker(superItf, marker));
}
}
}
}
ImmutableSet.Builder<DexType> commonBuilder = ImmutableSet.builder();
for (Map.Entry<DexType, Set<InterfaceMarker>> entry : seen.entrySet()) {
// Keep commonly visited interfaces only
if (entry.getValue().size() < 2) {
continue;
}
commonBuilder.add(entry.getKey());
}
Set<DexType> commonlyVisited = commonBuilder.build();
ImmutableSet.Builder<DexType> lubBuilder = ImmutableSet.builder();
for (DexType itf : commonlyVisited) {
// If there is a strict sub interface of this interface, it is not the least element.
if (commonlyVisited.stream().anyMatch(other -> other.isStrictSubtypeOf(itf, appInfo))) {
continue;
}
lubBuilder.add(itf);
}
return lubBuilder.build();
}
private static Set<DexType> computeLeastUpperBoundOfInterfaces(
AppInfo appInfo, Set<DexType> interfaces) {
return computeLeastUpperBoundOfInterfaces(appInfo, interfaces, interfaces);
}
public static BinaryOperator<TypeLatticeElement> joiner(AppInfo appInfo) {
return (l1, l2) -> l1.join(l2, appInfo);
}
public static TypeLatticeElement join(Stream<TypeLatticeElement> types, AppInfo appInfo) {
BinaryOperator<TypeLatticeElement> joiner = joiner(appInfo);
return types.reduce(BottomTypeLatticeElement.getInstance(), joiner, joiner);
}
public static TypeLatticeElement joinTypes(
Iterable<DexType> types, boolean isNullable, AppInfo appInfo) {
return join(Streams.stream(types).map(t -> fromDexType(t, isNullable, appInfo)), appInfo);
}
/**
* Determines the strict partial order of the given {@link TypeLatticeElement}s.
*
* @param other expected to be *strictly* bigger than {@param this}
* @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
* @return {@code true} if {@param this} is strictly less than {@param other}.
*/
public boolean strictlyLessThan(TypeLatticeElement other, AppInfo appInfo) {
if (equals(other)) {
return false;
}
TypeLatticeElement lub = join(other, appInfo);
return !equals(lub) && other.equals(lub);
}
/**
* Determines the partial order of the given {@link TypeLatticeElement}s.
*
* @param other expected to be bigger than or equal to {@param this}
* @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
* @return {@code true} if {@param this} is less than or equal to {@param other}.
*/
public boolean lessThanOrEqual(TypeLatticeElement other, AppInfo appInfo) {
return equals(other) || strictlyLessThan(other, appInfo);
}
/**
* Represents a type that can be everything.
*
* @return {@code true} if the corresponding {@link Value} could be any kinds.
*/
public boolean isTop() {
return false;
}
/**
* Represents an empty type.
*
* @return {@code true} if the type of corresponding {@link Value} is not determined yet.
*/
public boolean isBottom() {
return false;
}
public boolean isReference() {
return false;
}
public boolean isArrayType() {
return false;
}
public ArrayTypeLatticeElement asArrayTypeLatticeElement() {
return null;
}
public boolean isClassType() {
return false;
}
public ClassTypeLatticeElement asClassTypeLatticeElement() {
return null;
}
public boolean isPrimitive() {
return false;
}
public PrimitiveTypeLatticeElement asPrimitiveTypeLatticeElement() {
return null;
}
public boolean isSingle() {
return false;
}
public boolean isWide() {
return false;
}
public boolean isInt() {
return false;
}
public boolean isFloat() {
return false;
}
public boolean isLong() {
return false;
}
public boolean isDouble() {
return false;
}
public boolean isPreciseType() {
return isArrayType()
|| isClassType()
|| isInt()
|| isFloat()
|| isLong()
|| isDouble();
}
public boolean isNull() {
return false;
}
public boolean isReferenceInstance() {
return false;
}
public int requiredRegisters() {
assert !isBottom() && !isTop();
return isWide() ? 2 : 1;
}
public static ClassTypeLatticeElement objectClassType(AppInfo appInfo, boolean isNullable) {
return new ClassTypeLatticeElement(appInfo.dexItemFactory.objectType, isNullable);
}
static ArrayTypeLatticeElement objectArrayType(AppInfo appInfo, int nesting, boolean isNullable) {
return new ArrayTypeLatticeElement(
appInfo.dexItemFactory.createArrayType(nesting, appInfo.dexItemFactory.objectType),
isNullable);
}
public static TypeLatticeElement classClassType(AppInfo appInfo) {
return fromDexType(appInfo.dexItemFactory.classType, false, appInfo);
}
public static TypeLatticeElement stringClassType(AppInfo appInfo) {
return fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
}
public static TypeLatticeElement fromDexType(DexType type, boolean isNullable, AppInfo appInfo) {
if (type == DexItemFactory.nullValueType) {
return NULL;
}
if (type.isPrimitiveType()) {
return PrimitiveTypeLatticeElement.fromDexType(type);
}
if (type.isClassType()) {
if (!type.isUnknown() && type.isInterface()) {
return new ClassTypeLatticeElement(
appInfo.dexItemFactory.objectType, isNullable, ImmutableSet.of(type));
}
return new ClassTypeLatticeElement(type, isNullable,
computeLeastUpperBoundOfInterfaces(appInfo, type.implementedInterfaces(appInfo)));
}
assert type.isArrayType();
return new ArrayTypeLatticeElement(type, isNullable);
}
public static TypeLatticeElement fromDexType(DexType type) {
if (type == DexItemFactory.nullValueType) {
return NULL;
}
return fromTypeDescriptorChar((char) type.descriptor.content[0]);
}
public static TypeLatticeElement fromTypeDescriptorChar(char descriptor) {
switch (descriptor) {
case 'L':
// TODO(jsjeon): class type with Object?
case '[':
// TODO(jsjeon): array type with Object?
return REFERENCE;
default:
return PrimitiveTypeLatticeElement.fromTypeDescriptorChar(descriptor);
}
}
public static TypeLatticeElement fromMemberType(MemberType type) {
switch (type) {
case BOOLEAN:
case BYTE:
case CHAR:
case SHORT:
case INT:
return INT;
case FLOAT:
return FLOAT;
case INT_OR_FLOAT:
return SINGLE;
case LONG:
return LONG;
case DOUBLE:
return DOUBLE;
case LONG_OR_DOUBLE:
return WIDE;
case OBJECT:
return REFERENCE;
default:
throw new Unreachable("Unexpected member type: " + type);
}
}
public static TypeLatticeElement newArray(DexType arrayType, boolean isNullable) {
return new ArrayTypeLatticeElement(arrayType, isNullable);
}
public TypeLatticeElement arrayGet(AppInfo appInfo) {
return BOTTOM;
}
public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
TypeLatticeElement castTypeLattice = fromDexType(castType, isNullable(), appInfo);
if (lessThanOrEqual(castTypeLattice, appInfo)) {
return this;
}
return castTypeLattice;
}
@Override
abstract public String toString();
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || o.getClass() != this.getClass()) {
return false;
}
TypeLatticeElement otherElement = (TypeLatticeElement) o;
return otherElement.isNullable() == isNullable;
}
@Override
public int hashCode() {
return isNullable ? 1 : -1;
}
}