blob: b9db352a20ab40db6ae8c897b9f680390835d5bb [file] [log] [blame]
// Copyright (c) 2014, 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.
import 'optimizers.dart' show Pass, ParentVisitor;
import '../constants/constant_system.dart';
import '../constants/expressions.dart';
import '../resolution/operators.dart';
import '../constants/values.dart';
import '../dart_types.dart' as types;
import '../dart2jslib.dart' as dart2js;
import '../tree/tree.dart' show LiteralDartString;
import 'cps_ir_nodes.dart';
import '../types/types.dart' show TypeMask, TypesTask;
import '../types/constants.dart' show computeTypeMask;
import '../elements/elements.dart' show ClassElement, Element, Entity,
FieldElement, FunctionElement, ParameterElement;
import '../dart2jslib.dart' show ClassWorld;
import '../universe/universe.dart';
abstract class TypeSystem<T> {
T get dynamicType;
T get typeType;
T get functionType;
T get boolType;
T get intType;
T get stringType;
T get listType;
T get mapType;
T get nonNullType;
T getReturnType(FunctionElement element);
T getSelectorReturnType(Selector selector);
T getParameterType(ParameterElement element);
T getFieldType(FieldElement element);
T join(T a, T b);
T exact(ClassElement element);
T getTypeOf(ConstantValue constant);
bool areDisjoint(T leftType, T rightType);
/// True if all values satisfying [type] are booleans (null is not a boolean).
bool isDefinitelyBool(T type);
bool isDefinitelyNotNull(T type);
}
class UnitTypeSystem implements TypeSystem<String> {
static const String UNIT = 'unit';
get boolType => UNIT;
get dynamicType => UNIT;
get functionType => UNIT;
get intType => UNIT;
get listType => UNIT;
get mapType => UNIT;
get stringType => UNIT;
get typeType => UNIT;
get nonNullType => UNIT;
getParameterType(_) => UNIT;
getReturnType(_) => UNIT;
getSelectorReturnType(_) => UNIT;
getFieldType(_) => UNIT;
join(a, b) => UNIT;
exact(_) => UNIT;
getTypeOf(_) => UNIT;
bool isDefinitelyBool(_) => false;
bool isDefinitelyNotNull(_) => false;
bool areDisjoint(String leftType, String rightType) {
return false;
}
}
class TypeMaskSystem implements TypeSystem<TypeMask> {
final TypesTask inferrer;
final ClassWorld classWorld;
TypeMask get dynamicType => inferrer.dynamicType;
TypeMask get typeType => inferrer.typeType;
TypeMask get functionType => inferrer.functionType;
TypeMask get boolType => inferrer.boolType;
TypeMask get intType => inferrer.intType;
TypeMask get stringType => inferrer.stringType;
TypeMask get listType => inferrer.listType;
TypeMask get mapType => inferrer.mapType;
TypeMask get nonNullType => inferrer.nonNullType;
// TODO(karlklose): remove compiler here.
TypeMaskSystem(dart2js.Compiler compiler)
: inferrer = compiler.typesTask,
classWorld = compiler.world;
TypeMask getParameterType(ParameterElement parameter) {
return inferrer.getGuaranteedTypeOfElement(parameter);
}
TypeMask getReturnType(FunctionElement function) {
return inferrer.getGuaranteedReturnTypeOfElement(function);
}
TypeMask getSelectorReturnType(Selector selector) {
return inferrer.getGuaranteedTypeOfSelector(selector);
}
TypeMask getFieldType(FieldElement field) {
return inferrer.getGuaranteedTypeOfElement(field);
}
@override
TypeMask join(TypeMask a, TypeMask b) {
return a.union(b, classWorld);
}
@override
TypeMask getTypeOf(ConstantValue constant) {
return computeTypeMask(inferrer.compiler, constant);
}
TypeMask exact(ClassElement element) {
// The class world does not know about classes created by
// closure conversion, so just treat those as a subtypes of Function.
// TODO(asgerf): Maybe closure conversion should create a new ClassWorld?
if (element.isClosure) return functionType;
return new TypeMask.exact(element, classWorld);
}
bool isDefinitelyBool(TypeMask t) {
return t.containsOnlyBool(classWorld) && !t.isNullable;
}
bool isDefinitelyNotNull(TypeMask t) => !t.isNullable;
@override
bool areDisjoint(TypeMask leftType, TypeMask rightType) {
TypeMask intersection = leftType.intersection(rightType, classWorld);
return intersection.isEmpty && !intersection.isNullable;
}
}
/**
* Propagates types (including value types for constants) throughout the IR, and
* replaces branches with fixed jumps as well as side-effect free expressions
* with known constant results.
*
* Should be followed by the [ShrinkingReducer] pass.
*
* Implemented according to 'Constant Propagation with Conditional Branches'
* by Wegman, Zadeck.
*/
class TypePropagator<T> extends Pass {
String get passName => 'Sparse constant propagation';
final types.DartTypes _dartTypes;
// The constant system is used for evaluation of expressions with constant
// arguments.
final ConstantSystem _constantSystem;
final TypeSystem _typeSystem;
final dart2js.InternalErrorFunction _internalError;
final Map<Node, _AbstractValue> _types;
TypePropagator(this._dartTypes,
this._constantSystem,
this._typeSystem,
this._internalError)
: _types = <Node, _AbstractValue>{};
@override
void rewrite(FunctionDefinition root) {
// Set all parent pointers.
new ParentVisitor().visit(root);
// Analyze. In this phase, the entire term is analyzed for reachability
// and the abstract value of each expression.
_TypePropagationVisitor<T> analyzer = new _TypePropagationVisitor<T>(
_constantSystem,
_typeSystem,
_types,
_internalError,
_dartTypes);
analyzer.analyze(root);
// Transform. Uses the data acquired in the previous analysis phase to
// replace branches with fixed targets and side-effect-free expressions
// with constant results.
_TransformingVisitor<T> transformer = new _TransformingVisitor<T>(
analyzer.reachableNodes, analyzer.values, _internalError, _typeSystem);
transformer.transform(root);
}
getType(Node node) => _types[node];
}
/**
* Uses the information from a preceding analysis pass in order to perform the
* actual transformations on the CPS graph.
*/
class _TransformingVisitor<T> extends RecursiveVisitor {
final Set<Node> reachable;
final Map<Node, _AbstractValue> values;
final TypeSystem<T> typeSystem;
final dart2js.InternalErrorFunction internalError;
_TransformingVisitor(this.reachable,
this.values,
this.internalError,
this.typeSystem);
void transform(FunctionDefinition root) {
visit(root);
}
/// Given an expression with a known constant result and a continuation,
/// replaces the expression by a new LetPrim / InvokeContinuation construct.
/// `unlink` is a closure responsible for unlinking all removed references.
LetPrim constifyExpression(Expression node,
Continuation continuation,
void unlink()) {
_AbstractValue value = values[node];
if (value == null || !value.isConstant) {
return null;
}
assert(continuation.parameters.length == 1);
// Set up the replacement structure.
PrimitiveConstantValue primitiveConstant = value.constant;
ConstantExpression constExp =
const ConstantExpressionCreator().convert(primitiveConstant);
Constant constant = new Constant(constExp, primitiveConstant);
LetPrim letPrim = new LetPrim(constant);
InvokeContinuation invoke =
new InvokeContinuation(continuation, <Primitive>[constant]);
invoke.parent = constant.parent = letPrim;
letPrim.body = invoke;
// Replace the method invocation.
InteriorNode parent = node.parent;
letPrim.parent = parent;
parent.body = letPrim;
unlink();
return letPrim;
}
// A branch can be eliminated and replaced by an invocation if only one of
// the possible continuations is reachable. Removal often leads to both dead
// primitives (the condition variable) and dead continuations (the unreachable
// branch), which are both removed by the shrinking reductions pass.
//
// (Branch (IsTrue true) k0 k1) -> (InvokeContinuation k0)
void visitBranch(Branch node) {
bool trueReachable = reachable.contains(node.trueContinuation.definition);
bool falseReachable = reachable.contains(node.falseContinuation.definition);
bool bothReachable = (trueReachable && falseReachable);
bool noneReachable = !(trueReachable || falseReachable);
if (bothReachable || noneReachable) {
// Nothing to do, shrinking reductions take care of the unreachable case.
super.visitBranch(node);
return;
}
Continuation successor = (trueReachable) ?
node.trueContinuation.definition : node.falseContinuation.definition;
// Replace the branch by a continuation invocation.
assert(successor.parameters.isEmpty);
InvokeContinuation invoke =
new InvokeContinuation(successor, <Primitive>[]);
InteriorNode parent = node.parent;
invoke.parent = parent;
parent.body = invoke;
// Unlink all removed references.
node.trueContinuation.unlink();
node.falseContinuation.unlink();
IsTrue isTrue = node.condition;
isTrue.value.unlink();
visitInvokeContinuation(invoke);
}
// Side-effect free method calls with constant results can be replaced by
// a LetPrim / InvokeContinuation pair. May lead to dead primitives which
// are removed by the shrinking reductions pass.
//
// (InvokeMethod v0 == v1 k0)
// -> (assuming the result is a constant `true`)
// (LetPrim v2 (Constant true))
// (InvokeContinuation k0 v2)
void visitInvokeMethod(InvokeMethod node) {
Continuation cont = node.continuation.definition;
LetPrim letPrim = constifyExpression(node, cont, () {
node.receiver.unlink();
node.continuation.unlink();
node.arguments.forEach((Reference ref) => ref.unlink());
});
if (letPrim == null) {
_AbstractValue<T> receiver = getValue(node.receiver.definition);
node.receiverIsNotNull = receiver.isDefinitelyNotNull(typeSystem);
super.visitInvokeMethod(node);
} else {
visitLetPrim(letPrim);
}
}
// See [visitInvokeMethod].
void visitConcatenateStrings(ConcatenateStrings node) {
Continuation cont = node.continuation.definition;
LetPrim letPrim = constifyExpression(node, cont, () {
node.continuation.unlink();
node.arguments.forEach((Reference ref) => ref.unlink());
});
if (letPrim == null) {
super.visitConcatenateStrings(node);
} else {
visitLetPrim(letPrim);
}
}
// See [visitInvokeMethod].
void visitTypeOperator(TypeOperator node) {
Continuation cont = node.continuation.definition;
LetPrim letPrim = constifyExpression(node, cont, () {
node.value.unlink();
node.typeArguments.forEach((Reference ref) => ref.unlink());
node.continuation.unlink();
});
if (letPrim == null) {
super.visitTypeOperator(node);
} else {
visitLetPrim(letPrim);
}
}
_AbstractValue<T> getValue(Primitive primitive) {
_AbstractValue<T> value = values[primitive];
return value == null ? new _AbstractValue.nothing() : value;
}
void visitIdentical(Identical node) {
Primitive left = node.left.definition;
Primitive right = node.right.definition;
_AbstractValue<T> leftValue = getValue(left);
_AbstractValue<T> rightValue = getValue(right);
// Replace identical(x, true) by x when x is known to be a boolean.
if (leftValue.isDefinitelyBool(typeSystem) &&
rightValue.isConstant &&
rightValue.constant.isTrue) {
left.substituteFor(node);
}
}
}
/**
* Runs an analysis pass on the given function definition in order to detect
* const-ness as well as reachability, both of which are used in the subsequent
* transformation pass.
*/
class _TypePropagationVisitor<T> implements Visitor {
// The node worklist stores nodes that are both reachable and need to be
// processed, but have not been processed yet. Using a worklist avoids deep
// recursion.
// The node worklist and the reachable set operate in concert: nodes are
// only ever added to the worklist when they have not yet been marked as
// reachable, and adding a node to the worklist is always followed by marking
// it reachable.
// TODO(jgruber): Storing reachability per-edge instead of per-node would
// allow for further optimizations.
final List<Node> nodeWorklist = <Node>[];
final Set<Node> reachableNodes = new Set<Node>();
// The definition workset stores all definitions which need to be reprocessed
// since their lattice value has changed.
final Set<Definition> defWorkset = new Set<Definition>();
final ConstantSystem constantSystem;
final TypeSystem<T> typeSystem;
final dart2js.InternalErrorFunction internalError;
final types.DartTypes _dartTypes;
_AbstractValue<T> nothing = new _AbstractValue.nothing();
_AbstractValue<T> nonConstant([T type]) {
if (type == null) {
type = typeSystem.dynamicType;
}
return new _AbstractValue<T>.nonConstant(type);
}
_AbstractValue<T> constantValue(ConstantValue constant, T type) {
return new _AbstractValue<T>.constantValue(constant, type);
}
// Stores the current lattice value for nodes. Note that it contains not only
// definitions as keys, but also expressions such as method invokes.
// Access through [getValue] and [setValue].
final Map<Node, _AbstractValue<T>> values;
_TypePropagationVisitor(this.constantSystem,
TypeSystem typeSystem,
this.values,
this.internalError,
this._dartTypes)
: this.typeSystem = typeSystem;
void analyze(FunctionDefinition root) {
reachableNodes.clear();
defWorkset.clear();
nodeWorklist.clear();
// Initially, only the root node is reachable.
setReachable(root);
while (true) {
if (nodeWorklist.isNotEmpty) {
// Process a new reachable expression.
Node node = nodeWorklist.removeLast();
visit(node);
} else if (defWorkset.isNotEmpty) {
// Process all usages of a changed definition.
Definition def = defWorkset.first;
defWorkset.remove(def);
// Visit all uses of this definition. This might add new entries to
// [nodeWorklist], for example by visiting a newly-constant usage within
// a branch node.
for (Reference ref = def.firstRef; ref != null; ref = ref.next) {
visit(ref.parent);
}
} else {
break; // Both worklists empty.
}
}
}
/// If the passed node is not yet reachable, mark it reachable and add it
/// to the work list.
void setReachable(Node node) {
if (!reachableNodes.contains(node)) {
reachableNodes.add(node);
nodeWorklist.add(node);
}
}
/// Returns the lattice value corresponding to [node], defaulting to nothing.
///
/// Never returns null.
_AbstractValue<T> getValue(Node node) {
_AbstractValue<T> value = values[node];
return (value == null) ? nothing : value;
}
/// Joins the passed lattice [updateValue] to the current value of [node],
/// and adds it to the definition work set if it has changed and [node] is
/// a definition.
void setValue(Node node, _AbstractValue<T> updateValue) {
_AbstractValue<T> oldValue = getValue(node);
_AbstractValue<T> newValue = updateValue.join(oldValue, typeSystem);
if (oldValue == newValue) {
return;
}
// Values may only move in the direction NOTHING -> CONSTANT -> NONCONST.
assert(newValue.kind >= oldValue.kind);
values[node] = newValue;
if (node is Definition) {
defWorkset.add(node);
}
}
// -------------------------- Visitor overrides ------------------------------
void visit(Node node) { node.accept(this); }
void visitFunctionDefinition(FunctionDefinition node) {
if (node.thisParameter != null) {
// TODO(asgerf): Use a more precise type for 'this'.
setValue(node.thisParameter, nonConstant(typeSystem.nonNullType));
}
node.parameters.forEach(visit);
setReachable(node.body);
}
void visitLetPrim(LetPrim node) {
visit(node.primitive); // No reason to delay visits to primitives.
setReachable(node.body);
}
void visitLetCont(LetCont node) {
// The continuation is only marked as reachable on use.
setReachable(node.body);
}
void visitLetHandler(LetHandler node) {
setReachable(node.body);
// The handler is assumed to be reachable (we could instead treat it as
// unreachable unless we find something reachable that might throw in the
// body --- it's not clear if we want to do that here or in some other
// pass). The handler parameters are assumed to be unknown.
//
// TODO(kmillikin): we should set the type of the exception and stack
// trace here. The way we do that depends on how we handle 'on T' catch
// clauses.
setReachable(node.handler);
for (Parameter param in node.handler.parameters) {
setValue(param, nonConstant());
}
}
void visitLetMutable(LetMutable node) {
setValue(node.variable, getValue(node.value.definition));
setReachable(node.body);
}
void visitInvokeStatic(InvokeStatic node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
assert(cont.parameters.length == 1);
Parameter returnValue = cont.parameters[0];
Entity target = node.target;
T returnType = target is FieldElement
? typeSystem.dynamicType
: typeSystem.getReturnType(node.target);
setValue(returnValue, nonConstant(returnType));
}
void visitInvokeContinuation(InvokeContinuation node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
// Forward the constant status of all continuation invokes to the
// continuation. Note that this is effectively a phi node in SSA terms.
for (int i = 0; i < node.arguments.length; i++) {
Definition def = node.arguments[i].definition;
_AbstractValue<T> cell = getValue(def);
setValue(cont.parameters[i], cell);
}
}
void visitInvokeMethod(InvokeMethod node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
/// Sets the value of both the current node and the target continuation
/// parameter.
void setValues(_AbstractValue<T> updateValue) {
setValue(node, updateValue);
Parameter returnValue = cont.parameters[0];
setValue(returnValue, updateValue);
}
_AbstractValue<T> lhs = getValue(node.receiver.definition);
if (lhs.isNothing) {
return; // And come back later.
} else if (lhs.isNonConst) {
setValues(nonConstant(typeSystem.getSelectorReturnType(node.selector)));
return;
} else if (!node.selector.isOperator) {
// TODO(jgruber): Handle known methods on constants such as String.length.
setValues(nonConstant());
return;
}
// Calculate the resulting constant if possible.
ConstantValue result;
String opname = node.selector.name;
if (node.selector.argumentCount == 0) {
// Unary operator.
if (opname == "unary-") {
opname = "-";
}
UnaryOperation operation = constantSystem.lookupUnary(
UnaryOperator.parse(opname));
if (operation != null) {
result = operation.fold(lhs.constant);
}
} else if (node.selector.argumentCount == 1) {
// Binary operator.
_AbstractValue<T> rhs = getValue(node.arguments[0].definition);
if (!rhs.isConstant) {
setValues(nonConstant());
return;
}
BinaryOperation operation = constantSystem.lookupBinary(
BinaryOperator.parse(opname));
if (operation != null) {
result = operation.fold(lhs.constant, rhs.constant);
}
}
// Update value of the continuation parameter. Again, this is effectively
// a phi.
if (result == null) {
setValues(nonConstant());
} else {
T type = typeSystem.getTypeOf(result);
setValues(constantValue(result, type));
}
}
void visitInvokeMethodDirectly(InvokeMethodDirectly node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
assert(cont.parameters.length == 1);
Parameter returnValue = cont.parameters[0];
// TODO(karlklose): lookup the function and get ites return type.
setValue(returnValue, nonConstant());
}
void visitInvokeConstructor(InvokeConstructor node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
assert(cont.parameters.length == 1);
Parameter returnValue = cont.parameters[0];
setValue(returnValue, nonConstant(typeSystem.getReturnType(node.target)));
}
void visitConcatenateStrings(ConcatenateStrings node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
void setValues(_AbstractValue<T> updateValue) {
setValue(node, updateValue);
Parameter returnValue = cont.parameters[0];
setValue(returnValue, updateValue);
}
// TODO(jgruber): Currently we only optimize if all arguments are string
// constants, but we could also handle cases such as "foo${42}".
bool allStringConstants = node.arguments.every((Reference ref) {
if (!(ref.definition is Constant)) {
return false;
}
Constant constant = ref.definition;
return constant != null && constant.value.isString;
});
T type = typeSystem.stringType;
assert(cont.parameters.length == 1);
if (allStringConstants) {
// All constant, we can concatenate ourselves.
Iterable<String> allStrings = node.arguments.map((Reference ref) {
Constant constant = ref.definition;
StringConstantValue stringConstant = constant.value;
return stringConstant.primitiveValue.slowToString();
});
LiteralDartString dartString = new LiteralDartString(allStrings.join());
ConstantValue constant = new StringConstantValue(dartString);
setValues(constantValue(constant, type));
} else {
setValues(nonConstant(type));
}
}
void visitThrow(Throw node) {
}
void visitRethrow(Rethrow node) {
}
void visitNonTailThrow(NonTailThrow node) {
internalError(null, 'found non-tail throw after they were eliminated');
}
void visitBranch(Branch node) {
IsTrue isTrue = node.condition;
_AbstractValue<T> conditionCell = getValue(isTrue.value.definition);
if (conditionCell.isNothing) {
return; // And come back later.
} else if (conditionCell.isNonConst) {
setReachable(node.trueContinuation.definition);
setReachable(node.falseContinuation.definition);
} else if (conditionCell.isConstant && !conditionCell.constant.isBool) {
// Treat non-bool constants in condition as non-const since they result
// in type errors in checked mode.
// TODO(jgruber): Default to false in unchecked mode.
setReachable(node.trueContinuation.definition);
setReachable(node.falseContinuation.definition);
setValue(isTrue.value.definition, nonConstant(typeSystem.boolType));
} else if (conditionCell.isConstant && conditionCell.constant.isBool) {
BoolConstantValue boolConstant = conditionCell.constant;
setReachable((boolConstant.isTrue) ?
node.trueContinuation.definition : node.falseContinuation.definition);
}
}
void visitTypeOperator(TypeOperator node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
void setValues(_AbstractValue<T> updateValue) {
setValue(node, updateValue);
Parameter returnValue = cont.parameters[0];
setValue(returnValue, updateValue);
}
if (node.isTypeCast) {
// TODO(jgruber): Add support for `as` casts.
setValues(nonConstant());
return;
}
_AbstractValue<T> cell = getValue(node.value.definition);
if (cell.isNothing) {
return; // And come back later.
} else if (cell.isConstant && node.type.kind == types.TypeKind.INTERFACE) {
// Receiver is a constant, perform is-checks at compile-time.
types.InterfaceType checkedType = node.type;
ConstantValue constant = cell.constant;
types.DartType constantType = constant.getType(_dartTypes.coreTypes);
T type = typeSystem.boolType;
_AbstractValue<T> result;
if (constant.isNull &&
checkedType != _dartTypes.coreTypes.nullType &&
checkedType != _dartTypes.coreTypes.objectType) {
// `(null is Type)` is true iff Type is in { Null, Object }.
result = constantValue(new FalseConstantValue(), type);
} else {
// Otherwise, perform a standard subtype check.
result = constantValue(
constantSystem.isSubtype(_dartTypes, constantType, checkedType)
? new TrueConstantValue()
: new FalseConstantValue(),
type);
}
setValues(result);
} else {
setValues(nonConstant(typeSystem.boolType));
}
}
void visitSetMutableVariable(SetMutableVariable node) {
setValue(node.variable.definition, getValue(node.value.definition));
setReachable(node.body);
}
void visitLiteralList(LiteralList node) {
// Constant lists are translated into (Constant ListConstant(...)) IR nodes,
// and thus LiteralList nodes are NonConst.
setValue(node, nonConstant(typeSystem.listType));
}
void visitLiteralMap(LiteralMap node) {
// Constant maps are translated into (Constant MapConstant(...)) IR nodes,
// and thus LiteralMap nodes are NonConst.
setValue(node, nonConstant(typeSystem.mapType));
}
void visitConstant(Constant node) {
ConstantValue value = node.value;
setValue(node, constantValue(value, typeSystem.getTypeOf(value)));
}
void visitCreateFunction(CreateFunction node) {
setReachable(node.definition);
ConstantValue constant =
new FunctionConstantValue(node.definition.element);
setValue(node, constantValue(constant, typeSystem.functionType));
}
void visitGetMutableVariable(GetMutableVariable node) {
setValue(node, getValue(node.variable.definition));
}
void visitMutableVariable(MutableVariable node) {
// [MutableVariable]s are bound either as parameters to
// [FunctionDefinition]s, by [LetMutable].
if (node.parent is FunctionDefinition) {
// Just like immutable parameters, the values of mutable parameters are
// never constant.
// TODO(karlklose): remove reference to the element model.
Entity source = node.hint;
T type = (source is ParameterElement)
? typeSystem.getParameterType(source)
: typeSystem.dynamicType;
setValue(node, nonConstant(type));
} else if (node.parent is LetMutable) {
// Mutable values bound by LetMutable could have known values.
} else {
internalError(node.hint, "Unexpected parent of MutableVariable");
}
}
void visitParameter(Parameter node) {
Entity source = node.hint;
// TODO(karlklose): remove reference to the element model.
T type = (source is ParameterElement)
? typeSystem.getParameterType(source)
: typeSystem.dynamicType;
if (node.parent is FunctionDefinition) {
// Functions may escape and thus their parameters must be non-constant.
setValue(node, nonConstant(type));
} else if (node.parent is Continuation) {
// Continuations on the other hand are local, and parameters can have
// some other abstract value than non-constant.
} else {
internalError(node.hint, "Unexpected parent of Parameter: ${node.parent}");
}
}
void visitContinuation(Continuation node) {
node.parameters.forEach(visit);
if (node.body != null) {
setReachable(node.body);
}
}
void visitGetStatic(GetStatic node) {
if (node.element.isFunction) {
setValue(node, nonConstant(typeSystem.functionType));
} else {
setValue(node, nonConstant(typeSystem.getFieldType(node.element)));
}
}
void visitSetStatic(SetStatic node) {
setReachable(node.body);
}
void visitGetLazyStatic(GetLazyStatic node) {
Continuation cont = node.continuation.definition;
setReachable(cont);
assert(cont.parameters.length == 1);
Parameter returnValue = cont.parameters[0];
setValue(returnValue, nonConstant(typeSystem.getFieldType(node.element)));
}
void visitIsTrue(IsTrue node) {
Branch branch = node.parent;
visitBranch(branch);
}
void visitIdentical(Identical node) {
_AbstractValue<T> leftConst = getValue(node.left.definition);
_AbstractValue<T> rightConst = getValue(node.right.definition);
ConstantValue leftValue = leftConst.constant;
ConstantValue rightValue = rightConst.constant;
if (leftConst.isNothing || rightConst.isNothing) {
// Come back later.
return;
} else if (!leftConst.isConstant || !rightConst.isConstant) {
T leftType = leftConst.type;
T rightType = rightConst.type;
if (typeSystem.areDisjoint(leftType, rightType)) {
setValue(node,
constantValue(new FalseConstantValue(), typeSystem.boolType));
} else {
setValue(node, nonConstant(typeSystem.boolType));
}
} else if (leftValue.isPrimitive && rightValue.isPrimitive) {
assert(leftConst.isConstant && rightConst.isConstant);
PrimitiveConstantValue left = leftValue;
PrimitiveConstantValue right = rightValue;
ConstantValue result =
new BoolConstantValue(left.primitiveValue == right.primitiveValue);
setValue(node, constantValue(result, typeSystem.boolType));
}
}
void visitInterceptor(Interceptor node) {
setReachable(node.input.definition);
_AbstractValue<T> value = getValue(node.input.definition);
if (!value.isNothing) {
setValue(node, nonConstant(typeSystem.nonNullType));
}
}
void visitGetField(GetField node) {
setValue(node, nonConstant(typeSystem.getFieldType(node.field)));
}
void visitSetField(SetField node) {
setReachable(node.body);
}
void visitCreateBox(CreateBox node) {
setValue(node, nonConstant(typeSystem.nonNullType));
}
void visitCreateInstance(CreateInstance node) {
setValue(node, nonConstant(typeSystem.exact(node.classElement)));
}
void visitReifyRuntimeType(ReifyRuntimeType node) {
setValue(node, nonConstant(typeSystem.typeType));
}
void visitReadTypeVariable(ReadTypeVariable node) {
// TODO(karlklose): come up with a type marker for JS entities or switch to
// real constants of type [Type].
setValue(node, nonConstant());
}
@override
visitTypeExpression(TypeExpression node) {
// TODO(karlklose): come up with a type marker for JS entities or switch to
// real constants of type [Type].
setValue(node, nonConstant());
}
void visitCreateInvocationMirror(CreateInvocationMirror node) {
// TODO(asgerf): Expose [Invocation] type.
setValue(node, nonConstant(typeSystem.nonNullType));
}
}
/// Represents the abstract value of a primitive value at some point in the
/// program. Abstract values of all kinds have a type [T].
///
/// The different kinds of abstract values represents the knowledge about the
/// constness of the value:
/// NOTHING: cannot have any value
/// CONSTANT: is a constant. The value is stored in the [constant] field,
/// and the type of the constant is in the [type] field.
/// NONCONST: not a constant, but [type] may hold some information.
class _AbstractValue<T> {
static const int NOTHING = 0;
static const int CONSTANT = 1;
static const int NONCONST = 2;
final int kind;
final ConstantValue constant;
final T type;
_AbstractValue._internal(this.kind, this.constant, this.type) {
assert(kind != CONSTANT || constant != null);
}
_AbstractValue.nothing()
: this._internal(NOTHING, null, null);
_AbstractValue.constantValue(ConstantValue constant, T type)
: this._internal(CONSTANT, constant, type);
_AbstractValue.nonConstant(T type)
: this._internal(NONCONST, null, type);
bool get isNothing => (kind == NOTHING);
bool get isConstant => (kind == CONSTANT);
bool get isNonConst => (kind == NONCONST);
int get hashCode {
int hash = kind * 31 + constant.hashCode * 59 + type.hashCode * 67;
return hash & 0x3fffffff;
}
bool operator ==(_AbstractValue that) {
return that.kind == this.kind &&
that.constant == this.constant &&
that.type == this.type;
}
String toString() {
switch (kind) {
case NOTHING: return "Nothing";
case CONSTANT: return "Constant: $constant: $type";
case NONCONST: return "Non-constant: $type";
default: assert(false);
}
return null;
}
/// Compute the join of two values in the lattice.
_AbstractValue join(_AbstractValue that, TypeSystem typeSystem) {
assert(that != null);
if (isNothing) {
return that;
} else if (that.isNothing) {
return this;
} else if (isConstant && that.isConstant && constant == that.constant) {
return this;
} else {
return new _AbstractValue.nonConstant(
typeSystem.join(this.type, that.type));
}
}
/// True if all members of this value are booleans.
bool isDefinitelyBool(TypeSystem<T> typeSystem) {
if (kind == NOTHING) return true;
return typeSystem.isDefinitelyBool(type);
}
/// True if null is not a member of this value.
bool isDefinitelyNotNull(TypeSystem<T> typeSystem) {
if (kind == NOTHING) return true;
if (kind == CONSTANT) return !constant.isNull;
return typeSystem.isDefinitelyNotNull(type);
}
}
class ConstantExpressionCreator
implements ConstantValueVisitor<ConstantExpression, dynamic> {
const ConstantExpressionCreator();
ConstantExpression convert(ConstantValue value) => value.accept(this, null);
@override
ConstantExpression visitBool(BoolConstantValue constant, _) {
return new BoolConstantExpression(constant.primitiveValue);
}
@override
ConstantExpression visitConstructed(ConstructedConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitConstructed");
}
@override
ConstantExpression visitDeferred(DeferredConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitDeferred");
}
@override
ConstantExpression visitDouble(DoubleConstantValue constant, arg) {
return new DoubleConstantExpression(constant.primitiveValue);
}
@override
ConstantExpression visitDummy(DummyConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitDummy");
}
@override
ConstantExpression visitFunction(FunctionConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitFunction");
}
@override
ConstantExpression visitInt(IntConstantValue constant, arg) {
return new IntConstantExpression(constant.primitiveValue);
}
@override
ConstantExpression visitInterceptor(InterceptorConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitInterceptor");
}
@override
ConstantExpression visitList(ListConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitList");
}
@override
ConstantExpression visitMap(MapConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitMap");
}
@override
ConstantExpression visitNull(NullConstantValue constant, arg) {
return new NullConstantExpression();
}
@override
ConstantExpression visitString(StringConstantValue constant, arg) {
return new StringConstantExpression(
constant.primitiveValue.slowToString());
}
@override
ConstantExpression visitType(TypeConstantValue constant, arg) {
throw new UnsupportedError("ConstantExpressionCreator.visitType");
}
}