blob: b8033f950293f9121baef88ad1ac8d855f1c97e0 [file] [log] [blame]
// Copyright (c) 2017, the Dart 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.
/// Type flow summary of a member, function or initializer.
library vm.transformations.type_flow.summary;
import 'dart:core' hide Type;
import 'package:kernel/ast.dart'
hide Statement, StatementVisitor, MapLiteralEntry;
import 'calls.dart';
import 'types.dart';
import 'utils.dart';
abstract class CallHandler {
Type applyCall(Call callSite, Selector selector, Args<Type> args,
{bool isResultUsed});
void typeCheckTriggered();
}
/// Base class for all statements in a summary.
abstract class Statement extends TypeExpr {
/// Index of this statement in the [Summary].
int index = -1;
Summary summary;
@override
Type getComputedType(List<Type> types) {
final type = types[index];
assert(type != null);
return type;
}
String get label => "t$index";
@override
String toString() => label;
/// Prints body of this statement.
String dump();
/// Visit this statement by calling a corresponding [visitor] method.
void accept(StatementVisitor visitor);
/// Execute this statement and compute its resulting type.
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler);
}
/// Statement visitor.
class StatementVisitor {
void visitDefault(TypeExpr expr) {}
void visitParameter(Parameter expr) => visitDefault(expr);
void visitNarrow(Narrow expr) => visitDefault(expr);
void visitJoin(Join expr) => visitDefault(expr);
void visitUse(Use expr) => visitDefault(expr);
void visitCall(Call expr) => visitDefault(expr);
void visitExtract(Extract expr) => visitDefault(expr);
void visitCreateConcreteType(CreateConcreteType expr) => visitDefault(expr);
void visitCreateRuntimeType(CreateRuntimeType expr) => visitDefault(expr);
void visitTypeCheck(TypeCheck expr) => visitDefault(expr);
}
/// Input parameter of the summary.
class Parameter extends Statement {
final String name;
// [staticType] is null if no narrowing should be performed. This happens for
// type parameters and for parameters whose type is narrowed by a [TypeCheck]
// statement.
final Type staticTypeForNarrowing;
Type defaultValue;
Type _argumentType = const EmptyType();
Parameter(this.name, this.staticTypeForNarrowing);
@override
String get label => "%$name";
@override
void accept(StatementVisitor visitor) => visitor.visitParameter(this);
@override
String dump() {
String text = "$label = _Parameter #$index";
if (staticTypeForNarrowing != null) {
text += " [$staticTypeForNarrowing]";
}
return text;
}
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) =>
throw 'Unable to apply _Parameter';
Type get argumentType => _argumentType;
void _observeArgumentType(Type argType, TypeHierarchy typeHierarchy) {
assert(argType.isSpecialized);
_argumentType = _argumentType.union(argType, typeHierarchy);
assert(_argumentType.isSpecialized);
}
Type _observeNotPassed(TypeHierarchy typeHierarchy) {
final Type argType = defaultValue.specialize(typeHierarchy);
_observeArgumentType(argType, typeHierarchy);
return argType;
}
}
/// Narrows down [arg] to [type].
class Narrow extends Statement {
TypeExpr arg;
Type type;
Narrow(this.arg, this.type);
@override
void accept(StatementVisitor visitor) => visitor.visitNarrow(this);
@override
String dump() => "$label = _Narrow ($arg to $type)";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) =>
arg.getComputedType(computedTypes).intersection(type, typeHierarchy);
}
/// A flavor of [Narrow] statement which narrows argument
/// to a non-nullable type and records if argument can be
/// null or not null.
class NarrowNotNull extends Narrow {
static const int canBeNullFlag = 1 << 0;
static const int canBeNotNullFlag = 1 << 1;
int _flags = 0;
NarrowNotNull(TypeExpr arg) : super(arg, const AnyType());
// Shared NarrowNotNull instances which are used when the outcome is
// known at summary creation time.
static final NarrowNotNull alwaysNotNull = NarrowNotNull(null)
.._flags = canBeNotNullFlag;
static final NarrowNotNull alwaysNull = NarrowNotNull(null)
.._flags = canBeNullFlag;
static final NarrowNotNull unknown = NarrowNotNull(null)
.._flags = canBeNullFlag | canBeNotNullFlag;
bool get isAlwaysNull => (_flags & canBeNotNullFlag) == 0;
bool get isAlwaysNotNull => (_flags & canBeNullFlag) == 0;
Type handleArgument(Type argType) {
if (argType is NullableType) {
final baseType = argType.baseType;
if (baseType is EmptyType) {
_flags |= canBeNullFlag;
} else {
_flags |= (canBeNullFlag | canBeNotNullFlag);
}
return baseType;
} else {
_flags |= canBeNotNullFlag;
return argType;
}
}
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) =>
handleArgument(arg.getComputedType(computedTypes));
}
/// Joins values from multiple sources. Its type is a union of [values].
class Join extends Statement {
final String _name;
final DartType staticType;
final List<TypeExpr> values = <TypeExpr>[]; // TODO(alexmarkov): Set
Join(this._name, this.staticType);
@override
String get label => _name ?? super.label;
@override
void accept(StatementVisitor visitor) => visitor.visitJoin(this);
@override
String dump() => "$label = _Join [${nodeToText(staticType)}]"
" (${values.join(", ")})";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
Type type = null;
assert(values.isNotEmpty);
for (var value in values) {
final valueType = value.getComputedType(computedTypes);
type = type != null ? type.union(valueType, typeHierarchy) : valueType;
}
return type;
}
}
/// Artificial use of [arg]. Removed during summary normalization.
class Use extends Statement {
TypeExpr arg;
Use(this.arg);
@override
void accept(StatementVisitor visitor) => visitor.visitUse(this);
@override
String dump() => "$label = _Use ($arg)";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) =>
throw 'Use statements should be removed during summary normalization';
}
/// Call site.
class Call extends Statement {
final Selector selector;
final Args<TypeExpr> args;
final Type staticResultType;
Call(this.selector, this.args, this.staticResultType) {
// TODO(sjindel/tfa): Support inferring unchecked entry-points for dynamic
// and direct calls as well.
if (selector is DynamicSelector || selector is DirectSelector) {
setUseCheckedEntry();
}
}
@override
void accept(StatementVisitor visitor) => visitor.visitCall(this);
@override
String dump() => "$label${isResultUsed ? '*' : ''} = _Call $selector $args";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
final List<Type> argTypes = new List<Type>.filled(args.values.length, null);
for (int i = 0; i < args.values.length; i++) {
final Type type = args.values[i].getComputedType(computedTypes);
if (type == const EmptyType()) {
debugPrint("Optimized call with empty arg");
return const EmptyType();
}
argTypes[i] = type;
}
setReachable();
if (selector is! DirectSelector) {
_observeReceiverType(argTypes[0], typeHierarchy);
}
final Stopwatch timer = kPrintTimings ? (new Stopwatch()..start()) : null;
Type result = callHandler.applyCall(
this, selector, new Args<Type>(argTypes, names: args.names),
isResultUsed: isResultUsed);
summary.calleeTime += kPrintTimings ? timer.elapsedMicroseconds : 0;
if (isResultUsed) {
if (staticResultType != null) {
result = result.intersection(staticResultType, typeHierarchy);
}
result = result.specialize(typeHierarchy);
_observeResultType(result, typeHierarchy);
}
return result;
}
// --- Inferred call site information. ---
int _flags = 0;
Type _resultType = const EmptyType();
static const int kMonomorphic = (1 << 0);
static const int kPolymorphic = (1 << 1);
static const int kNullableReceiver = (1 << 2);
static const int kResultUsed = (1 << 3);
static const int kReachable = (1 << 4);
static const int kUseCheckedEntry = (1 << 5);
static const int kReceiverMayBeInt = (1 << 6);
Member _monomorphicTarget;
Member get monomorphicTarget => _monomorphicTarget;
bool get isMonomorphic => (_flags & kMonomorphic) != 0;
bool get isPolymorphic => (_flags & kPolymorphic) != 0;
bool get isNullableReceiver => (_flags & kNullableReceiver) != 0;
bool get isResultUsed => (_flags & kResultUsed) != 0;
bool get isReachable => (_flags & kReachable) != 0;
bool get receiverMayBeInt => (_flags & kReceiverMayBeInt) != 0;
bool get useCheckedEntry => (_flags & kUseCheckedEntry) != 0;
Type get resultType => _resultType;
void setUseCheckedEntry() {
_flags |= kUseCheckedEntry;
}
void setResultUsed() {
_flags |= kResultUsed;
}
void setReachable() {
_flags |= kReachable;
}
void setPolymorphic() {
_flags = (_flags & ~kMonomorphic) | kPolymorphic;
_monomorphicTarget = null;
}
void addTarget(Member target) {
if (!isPolymorphic) {
if (isMonomorphic) {
if (_monomorphicTarget != target) {
setPolymorphic();
}
} else {
_flags |= kMonomorphic;
_monomorphicTarget = target;
}
}
}
void _observeReceiverType(Type receiver, TypeHierarchy typeHierarchy) {
if (receiver is NullableType) {
_flags |= kNullableReceiver;
}
final receiverIntIntersect =
receiver.intersection(typeHierarchy.intType, typeHierarchy);
if (receiverIntIntersect != EmptyType() &&
receiverIntIntersect != NullableType(EmptyType())) {
_flags |= kReceiverMayBeInt;
}
}
void _observeResultType(Type result, TypeHierarchy typeHierarchy) {
assert(result.isSpecialized);
_resultType = _resultType.union(result, typeHierarchy);
assert(_resultType.isSpecialized);
}
}
// Extract a type argument from a ConcreteType (used to extract type arguments
// from receivers of methods).
class Extract extends Statement {
TypeExpr arg;
final Class referenceClass;
final int paramIndex;
final Nullability nullability;
Extract(this.arg, this.referenceClass, this.paramIndex, this.nullability);
@override
void accept(StatementVisitor visitor) => visitor.visitExtract(this);
@override
String dump() => "$label = _Extract ($arg[${nodeToText(referenceClass)}"
"/$paramIndex]${nullability.suffix})";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
Type argType = arg.getComputedType(computedTypes);
Type extractedType;
void extractType(ConcreteType c) {
if (c.typeArgs == null) {
extractedType = const UnknownType();
} else {
final interfaceOffset = typeHierarchy.genericInterfaceOffsetFor(
c.cls.classNode, referenceClass);
final typeArg = c.typeArgs[interfaceOffset + paramIndex];
Type extracted = typeArg;
if (typeArg is RuntimeType) {
final argNullability = typeArg.nullability;
if (argNullability != nullability) {
// Apply nullability of type parameter type.
Nullability result;
if (argNullability == Nullability.nullable ||
nullability == Nullability.nullable) {
result = Nullability.nullable;
} else if (argNullability == Nullability.legacy ||
nullability == Nullability.legacy) {
result = Nullability.legacy;
} else {
result = Nullability.nonNullable;
}
if (argNullability != result) {
extracted = typeArg.withNullability(result);
}
}
} else {
assert(typeArg is UnknownType);
}
if (extractedType == null || extracted == extractedType) {
extractedType = extracted;
} else {
extractedType = const UnknownType();
}
}
}
// TODO(sjindel/tfa): Support more types here if possible.
if (argType is ConcreteType) {
extractType(argType);
} else if (argType is SetType) {
argType.types.forEach(extractType);
}
return extractedType ?? const UnknownType();
}
}
// Instantiate a concrete type with type arguments. For example, used to fill in
// "T = int" in "C<T>" to create "C<int>".
//
// The type arguments are factored against the generic interfaces; for more
// details see 'ClassHierarchyCache.factoredGenericInterfacesOf'.
class CreateConcreteType extends Statement {
final TFClass cls;
final List<TypeExpr> flattenedTypeArgs;
CreateConcreteType(this.cls, this.flattenedTypeArgs);
@override
void accept(StatementVisitor visitor) =>
visitor.visitCreateConcreteType(this);
@override
String dump() {
int numImmediateTypeArgs = cls.classNode.typeParameters.length;
return "$label = _CreateConcreteType ($cls @ "
"${flattenedTypeArgs.take(numImmediateTypeArgs)})";
}
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
bool hasRuntimeType = false;
final types = new List<Type>.filled(flattenedTypeArgs.length, null);
for (int i = 0; i < types.length; ++i) {
final computed = flattenedTypeArgs[i].getComputedType(computedTypes);
assert(computed is RuntimeType || computed is UnknownType);
if (computed is RuntimeType) hasRuntimeType = true;
types[i] = computed;
}
return new ConcreteType(cls, hasRuntimeType ? types : null);
}
}
// Similar to "CreateConcreteType", but creates a "RuntimeType" rather than a
// "ConcreteType". Unlike a "ConcreteType", none of the type arguments can be
// missing ("UnknownType").
class CreateRuntimeType extends Statement {
final Class klass;
final Nullability nullability;
final List<TypeExpr> flattenedTypeArgs;
CreateRuntimeType(this.klass, this.nullability, this.flattenedTypeArgs);
@override
void accept(StatementVisitor visitor) => visitor.visitCreateRuntimeType(this);
@override
String dump() => "$label = _CreateRuntimeType (${nodeToText(klass)} @ "
"${flattenedTypeArgs.take(klass.typeParameters.length)}"
"${nullability.suffix})";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
final types = new List<RuntimeType>.filled(flattenedTypeArgs.length, null);
for (int i = 0; i < types.length; ++i) {
final computed = flattenedTypeArgs[i].getComputedType(computedTypes);
assert(computed is RuntimeType || computed is UnknownType);
if (computed is UnknownType) return const UnknownType();
types[i] = computed;
}
DartType dartType;
if (klass == typeHierarchy.coreTypes.deprecatedFutureOrClass) {
dartType = new FutureOrType(const DynamicType(), nullability);
} else {
dartType = new InterfaceType(klass, nullability);
}
return new RuntimeType(dartType, types);
}
}
// Used to simulate a runtime type-check, to determine when it can be skipped.
// TODO(sjindel/tfa): Unify with Narrow.
class TypeCheck extends Statement {
TypeExpr arg;
TypeExpr type;
// The Kernel which this TypeCheck corresponds to. Can be a
// VariableDeclaration, AsExpression or Field.
//
// VariableDeclaration is used for parameter type-checks.
// Field is used for type-checks of parameters to implicit setters.
final TreeNode node;
final Type staticType;
// 'isTestedOnlyOnCheckedEntryPoint' is whether or not this parameter's type-check will
// occur on the "checked" entrypoint in the VM but will be skipped on
// "unchecked" entrypoint.
bool isTestedOnlyOnCheckedEntryPoint;
VariableDeclaration get parameter =>
node is VariableDeclaration ? node : null;
bool canAlwaysSkip = true;
TypeCheck(this.arg, this.type, this.node, this.staticType) {
assert(node != null);
isTestedOnlyOnCheckedEntryPoint =
parameter != null && !parameter.isCovariant;
}
@override
void accept(StatementVisitor visitor) => visitor.visitTypeCheck(this);
@override
String dump() {
String result = "$label = _TypeCheck ($arg against $type)";
result += " (for ${nodeToText(node)})";
return result;
}
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
Type argType = arg.getComputedType(computedTypes);
Type checkType = type.getComputedType(computedTypes);
// TODO(sjindel/tfa): Narrow the result if possible.
assert(checkType is UnknownType || checkType is RuntimeType);
bool canSkip = true; // Can this check be skipped on this invocation.
if (checkType is UnknownType) {
// If we don't know what the RHS of the check is going to be, we can't
// guarantee that it will pass.
canSkip = false;
} else if (checkType is RuntimeType) {
canSkip = argType.isSubtypeOfRuntimeType(typeHierarchy, checkType);
argType = argType.intersection(
typeHierarchy.fromStaticType(checkType.representedTypeRaw, true),
typeHierarchy);
} else {
throw "Cannot see $checkType on RHS of TypeCheck.";
}
// If this check might be skipped on an
// unchecked entry-point, we need to signal that the call-site must be
// checked.
if (!canSkip) {
canAlwaysSkip = false;
if (isTestedOnlyOnCheckedEntryPoint) {
callHandler.typeCheckTriggered();
}
if (kPrintTrace) {
tracePrint("TypeCheck of $argType against $checkType failed.");
}
}
argType = argType.intersection(staticType, typeHierarchy);
return argType;
}
}
/// Summary is a linear sequence of statements representing a type flow in
/// one member, function or initializer.
class Summary {
final String name;
int parameterCount;
int positionalParameterCount;
int requiredParameterCount;
List<Statement> _statements = <Statement>[];
TypeExpr result = null;
Type resultType = EmptyType();
// Analysis time of callees. Populated only if kPrintTimings.
int calleeTime;
Summary(this.name,
{this.parameterCount: 0,
this.positionalParameterCount: 0,
this.requiredParameterCount: 0});
List<Statement> get statements => _statements;
Statement add(Statement op) {
op.index = _statements.length;
op.summary = this;
_statements.add(op);
return op;
}
void reset() {
_statements = <Statement>[];
}
@override
String toString() {
return _statements.map((op) => op.dump()).join("\n") +
"\n" +
"RESULT: ${result}";
}
/// Apply this summary to the given arguments and return the resulting type.
Type apply(Args<Type> arguments, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
final Stopwatch timer = kPrintTimings ? (new Stopwatch()..start()) : null;
final int oldCalleeTime = calleeTime;
calleeTime = 0;
final args = arguments.values;
final positionalArgCount = arguments.positionalCount;
final namedArgCount = arguments.namedCount;
assert(requiredParameterCount <= positionalArgCount);
assert(positionalArgCount <= positionalParameterCount);
assert(namedArgCount <= parameterCount - positionalParameterCount);
// Interpret statements sequentially, calculating the result type
// of each statement and putting it into the 'types' list parallel
// to `_statements`.
//
// After normalization, statements can only reference preceding statements
// (they can't have forward references or loops).
//
// The first `parameterCount` statements are Parameters.
List<Type> types = new List<Type>.filled(_statements.length, null);
for (int i = 0; i < positionalArgCount; i++) {
final Parameter param = _statements[i] as Parameter;
if (args[i] is RuntimeType) {
types[i] = args[i];
continue;
}
final argType = args[i].specialize(typeHierarchy);
param._observeArgumentType(argType, typeHierarchy);
if (param.staticTypeForNarrowing != null) {
types[i] =
argType.intersection(param.staticTypeForNarrowing, typeHierarchy);
} else {
// TODO(sjindel/tfa): Narrowing is performed inside a [TypeCheck] later.
types[i] = args[i];
}
}
for (int i = positionalArgCount; i < positionalParameterCount; i++) {
types[i] = (_statements[i] as Parameter)._observeNotPassed(typeHierarchy);
}
final argNames = arguments.names;
int argIndex = 0;
for (int i = positionalParameterCount; i < parameterCount; i++) {
final Parameter param = _statements[i] as Parameter;
assert(param.defaultValue != null);
if ((argIndex < namedArgCount) && (argNames[argIndex] == param.name)) {
final argType =
args[positionalArgCount + argIndex].specialize(typeHierarchy);
argIndex++;
param._observeArgumentType(argType, typeHierarchy);
if (param.staticTypeForNarrowing != null) {
types[i] =
argType.intersection(param.staticTypeForNarrowing, typeHierarchy);
} else {
types[i] = argType;
}
} else {
assert((argIndex == namedArgCount) ||
(param.name.compareTo(argNames[argIndex]) < 0));
types[i] = param._observeNotPassed(typeHierarchy);
}
}
assert(argIndex == namedArgCount);
for (int i = parameterCount; i < _statements.length; i++) {
// Test if tracing is enabled to avoid expensive message formatting.
if (kPrintTrace) {
tracePrint("EVAL ${_statements[i].dump()}");
}
types[i] = _statements[i].apply(types, typeHierarchy, callHandler);
if (kPrintTrace) {
tracePrint("RESULT ${types[i]}");
}
}
Statistics.summariesAnalyzed++;
Type computedType = result.getComputedType(types);
resultType = resultType.union(computedType, typeHierarchy);
if (kPrintTimings) {
final dirtyTime = timer.elapsedMicroseconds;
final pureTime = dirtyTime < calleeTime ? 0 : (dirtyTime - calleeTime);
Statistics.numSummaryApplications.add(name);
Statistics.dirtySummaryAnalysisTime.add(name, dirtyTime);
Statistics.pureSummaryAnalysisTime.add(name, pureTime);
}
calleeTime = oldCalleeTime;
return computedType;
}
Args<Type> get argumentTypes {
final argTypes = new List<Type>.filled(parameterCount, null);
final argNames = new List<String>.filled(
parameterCount - positionalParameterCount, null);
for (int i = 0; i < parameterCount; i++) {
Parameter param = _statements[i] as Parameter;
argTypes[i] = param.argumentType;
if (i >= positionalParameterCount) {
argNames[i - positionalParameterCount] = param.name;
}
}
return new Args<Type>(argTypes, names: argNames);
}
Type argumentType(Member member, VariableDeclaration memberParam) {
final int firstParamIndex =
numTypeParams(member) + (hasReceiverArg(member) ? 1 : 0);
final positional = member.function.positionalParameters;
for (int i = 0; i < positional.length; i++) {
if (positional[i] == memberParam) {
final Parameter param = _statements[firstParamIndex + i] as Parameter;
assert(param.name == memberParam.name);
return param.argumentType;
}
}
for (int i = positionalParameterCount; i < parameterCount; i++) {
final Parameter param = _statements[i] as Parameter;
if (param.name == memberParam.name) {
return param.argumentType;
}
}
throw "Could not find argument type of parameter ${memberParam.name}";
}
List<VariableDeclaration> get uncheckedParameters {
final params = <VariableDeclaration>[];
for (Statement statement in _statements) {
if (statement is TypeCheck &&
statement.canAlwaysSkip &&
statement.parameter != null) {
params.add(statement.parameter);
}
}
return params;
}
/// Update the summary parameters to reflect a signature change with moved
/// and/or removed parameters.
void adjustFunctionParameters(Member member) {
// Just keep the parameters part of the summary, assuming that the rest is
// not used in later phases. The index values in the statements will be
// incorrect, but those are assumed to be not used either.
final int implicit =
(hasReceiverArg(member) ? 1 : 0) + numTypeParams(member);
final Map<String, Parameter> paramsByName = {};
for (int i = implicit; i < parameterCount; i++) {
final Parameter param = statements[i];
paramsByName[param.name] = param;
}
FunctionNode function = member.function;
statements.length = implicit;
for (VariableDeclaration param in function.positionalParameters) {
statements.add(paramsByName[param.name]);
}
positionalParameterCount = statements.length;
for (VariableDeclaration param in function.namedParameters) {
statements.add(paramsByName[param.name]);
}
parameterCount = statements.length;
requiredParameterCount = implicit + function.requiredParameterCount;
}
}