| Index: lib/compiler/implementation/ssa/nodes.dart
|
| diff --git a/lib/compiler/implementation/ssa/nodes.dart b/lib/compiler/implementation/ssa/nodes.dart
|
| index 64b85d6987875bca57fb19d0aa5045f040fec44b..30134858fcc2127d3aef2a4a40d8191ec159a48e 100644
|
| --- a/lib/compiler/implementation/ssa/nodes.dart
|
| +++ b/lib/compiler/implementation/ssa/nodes.dart
|
| @@ -788,7 +788,8 @@ class HType {
|
| bool isNumber() => (this.flag & (FLAG_INTEGER | FLAG_DOUBLE)) != 0;
|
| bool isStringOrArray() =>
|
| (this.flag & (FLAG_STRING | FLAG_READABLE_ARRAY)) != 0;
|
| - bool isKnown() => this !== UNKNOWN && this !== CONFLICTING;
|
| + /** A type is useful it is not unknown and not conflicting. */
|
| + bool isUseful() => this !== UNKNOWN && this !== CONFLICTING;
|
|
|
| static HType getTypeFromFlag(int flag) {
|
| if (flag === CONFLICTING.flag) return CONFLICTING;
|
| @@ -836,7 +837,6 @@ class HInstruction implements Hashable {
|
| HInstruction previous = null;
|
| HInstruction next = null;
|
| int flags = 0;
|
| - HType type = HType.UNKNOWN;
|
|
|
| // Changes flags.
|
| static final int FLAG_CHANGES_SOMETHING = 0;
|
| @@ -848,7 +848,11 @@ class HInstruction implements Hashable {
|
| // Other flags.
|
| static final int FLAG_USE_GVN = FLAG_DEPENDS_ON_SOMETHING + 1;
|
|
|
| - HInstruction(this.inputs) : id = idCounter++, usedBy = <HInstruction>[];
|
| + HInstruction(this.inputs)
|
| + : id = idCounter++,
|
| + usedBy = <HInstruction>[] {
|
| + if (guaranteedType.isUseful()) propagatedType = guaranteedType;
|
| + }
|
|
|
| int hashCode() => id;
|
|
|
| @@ -870,24 +874,68 @@ class HInstruction implements Hashable {
|
| // Does this node potentially affect control flow.
|
| bool isControlFlow() => false;
|
|
|
| - bool isArray() => type.isArray();
|
| - bool isMutableArray() => type.isMutableArray();
|
| - bool isBoolean() => type.isBoolean();
|
| - bool isInteger() => type.isInteger();
|
| - bool isNumber() => type.isNumber();
|
| - bool isString() => type.isString();
|
| - bool isTypeUnknown() => type.isUnknown();
|
| - bool isStringOrArray() => type.isStringOrArray();
|
| + // All isFunctions work on the propagated types.
|
| + bool isArray() => propagatedType.isArray();
|
| + bool isMutableArray() => propagatedType.isMutableArray();
|
| + bool isBoolean() => propagatedType.isBoolean();
|
| + bool isInteger() => propagatedType.isInteger();
|
| + bool isDouble() => propagatedType.isDouble();
|
| + bool isNumber() => propagatedType.isNumber();
|
| + bool isString() => propagatedType.isString();
|
| + bool isTypeUnknown() => propagatedType.isUnknown();
|
| + bool isStringOrArray() => propagatedType.isStringOrArray();
|
|
|
| - // Compute the type of the instruction.
|
| - HType computeType() => HType.UNKNOWN;
|
| + /**
|
| + * This is the type the instruction is guaranteed to have. It does not
|
| + * take any propagation into account.
|
| + */
|
| + HType get guaranteedType() => HType.UNKNOWN;
|
| + bool hasGuaranteedType() => !guaranteedType.isUnknown();
|
|
|
| - HType computeDesiredInputType(HInstruction input) => HType.UNKNOWN;
|
| + /**
|
| + * The [propagatedType] is the type the instruction is assumed to have.
|
| + * Without speculative type assumptions it is computed frome the propagated
|
| + * type of the instruction's inputs and does not any guess work.
|
| + *
|
| + * With speculative types [computeTypeFromInputTypes()] and [propagatedType]
|
| + * may differ. In this case the instruction's type must be guarded.
|
| + *
|
| + * Note that the [propagatedType] may only be set to [HType.CONFLICTING] with
|
| + * speculative types (as otherwise the instruction either sets the output
|
| + * type to [HType.UNKNOWN] or a specific type.
|
| + */
|
| + HType propagatedType = HType.UNKNOWN;
|
|
|
| - // Returns whether the instruction does produce the type it claims.
|
| - // For most instructions, this returns false. A type guard will be
|
| - // inserted to make sure the users get the right type in.
|
| - bool hasExpectedType() => false;
|
| + /**
|
| + * Some instructions have a good idea of their return type, but cannot
|
| + * guarantee the type. The [likelyType] does not need to be more specialized
|
| + * than the [propagatedType].
|
| + *
|
| + * Examples: the [likelyType] of [:x == y:] is a boolean. In most cases this
|
| + * cannot be guaranteed, but when merging types we still want to use this
|
| + * information.
|
| + *
|
| + * Similarily the [HAdd] instruction is likely a number. Note that, even if
|
| + * the [propagatedType] is already set to integer, the [likelyType] still
|
| + * might just return the number type.
|
| + */
|
| + HType get likelyType() => propagatedType;
|
| +
|
| + /**
|
| + * Compute the type of the instruction by propagating the input types through
|
| + * the instruction.
|
| + *
|
| + * By default just copy the guaranteed type.
|
| + */
|
| + HType computeTypeFromInputTypes() => guaranteedType;
|
| +
|
| + /**
|
| + * Compute the desired type for the the given [input]. Aside from using
|
| + * other inputs to compute the desired type one should also use
|
| + * the [propagatedType] which, during the invocation of this method,
|
| + * represents the desired type of [this].
|
| + */
|
| + HType computeDesiredTypeForInput(HInstruction input) => HType.UNKNOWN;
|
|
|
| bool isInBasicBlock() => block !== null;
|
|
|
| @@ -1001,8 +1049,7 @@ class HBoolify extends HInstruction {
|
| setUseGvn();
|
| }
|
|
|
| - HType computeType() => HType.BOOLEAN;
|
| - bool hasExpectedType() => true;
|
| + HType get guaranteedType() => HType.BOOLEAN;
|
|
|
| accept(HVisitor visitor) => visitor.visitBoolify(this);
|
| int typeCode() => 0;
|
| @@ -1030,21 +1077,16 @@ class HTypeGuard extends HInstruction {
|
|
|
| HInstruction get guarded() => inputs.last();
|
|
|
| - HType computeType() => type;
|
| - bool hasExpectedType() => true;
|
| -
|
| bool isControlFlow() => true;
|
|
|
| accept(HVisitor visitor) => visitor.visitTypeGuard(this);
|
| int typeCode() => 1;
|
| bool typeEquals(other) => other is HTypeGuard;
|
| - bool dataEquals(HTypeGuard other) => type == other.type;
|
| + bool dataEquals(HTypeGuard other) => propagatedType == other.propagatedType;
|
| }
|
|
|
| class HBoundsCheck extends HCheck {
|
| - HBoundsCheck(length, index) : super(<HInstruction>[length, index]) {
|
| - type = HType.INTEGER;
|
| - }
|
| + HBoundsCheck(length, index) : super(<HInstruction>[length, index]);
|
|
|
| HInstruction get length() => inputs[0];
|
| HInstruction get index() => inputs[1];
|
| @@ -1054,8 +1096,7 @@ class HBoundsCheck extends HCheck {
|
| setUseGvn();
|
| }
|
|
|
| - HType computeType() => HType.INTEGER;
|
| - bool hasExpectedType() => true;
|
| + HType get guaranteedType() => HType.INTEGER;
|
|
|
| accept(HVisitor visitor) => visitor.visitBoundsCheck(this);
|
| int typeCode() => 2;
|
| @@ -1073,8 +1114,7 @@ class HIntegerCheck extends HCheck {
|
| setUseGvn();
|
| }
|
|
|
| - HType computeType() => HType.INTEGER;
|
| - bool hasExpectedType() => true;
|
| + HType get guaranteedType() => HType.INTEGER;
|
|
|
| accept(HVisitor visitor) => visitor.visitIntegerCheck(this);
|
| int typeCode() => 3;
|
| @@ -1179,15 +1219,24 @@ class HInvokeStatic extends HInvoke {
|
| && element.enclosingElement.name.slowToString() == 'List');
|
| }
|
|
|
| - HType computeType() {
|
| + HType get guaranteedType() {
|
| if (isArrayConstructor()) {
|
| return HType.MUTABLE_ARRAY;
|
| }
|
| return HType.UNKNOWN;
|
| }
|
|
|
| + HType computeDesiredTypeForInput(HInstruction input) {
|
| + // TODO(floitsch): we want the target to be a function.
|
| + if (input == target) return HType.UNKNOWN;
|
| + return computeDesiredTypeForNonTargetInput(input);
|
| + }
|
| +
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + return HType.UNKNOWN;
|
| + }
|
| +
|
| bool get builtin() => isArrayConstructor();
|
| - bool hasExpectedType() => isArrayConstructor();
|
| }
|
|
|
| class HInvokeSuper extends HInvokeStatic {
|
| @@ -1208,10 +1257,14 @@ class HInvokeInterceptor extends HInvokeStatic {
|
| toString() => 'invoke interceptor: ${element.name}';
|
| accept(HVisitor visitor) => visitor.visitInvokeInterceptor(this);
|
|
|
| - String get builtinJsName() {
|
| - if (getter
|
| + bool isLengthGetterOnStringOrArray() {
|
| + return getter
|
| && name == const SourceString('length')
|
| - && inputs[1].isStringOrArray()) {
|
| + && inputs[1].isStringOrArray();
|
| + }
|
| +
|
| + String get builtinJsName() {
|
| + if (isLengthGetterOnStringOrArray()) {
|
| return 'length';
|
| } else if (name == const SourceString('add')
|
| && inputs[1].isMutableArray()) {
|
| @@ -1223,17 +1276,23 @@ class HInvokeInterceptor extends HInvokeStatic {
|
| return null;
|
| }
|
|
|
| - HType computeType() {
|
| - if (getter
|
| - && name == const SourceString('length')
|
| - && inputs[1].isStringOrArray()) {
|
| - return HType.INTEGER;
|
| - }
|
| + HType get guaranteedType() => HType.UNKNOWN;
|
| +
|
| + HType get likelyType() {
|
| + // In general a length getter or method returns an int.
|
| + if (name == const SourceString('length')) return HType.INTEGER;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - if (input == inputs[0]) return HType.UNKNOWN;
|
| + HType computeTypeFromInputTypes() {
|
| + if (isLengthGetterOnStringOrArray()) return HType.INTEGER;
|
| + return HType.UNKNOWN;
|
| + }
|
| +
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // If the first argument is a string or an array and we invoke methods
|
| + // on it that mutate it, then we want to restrict the incoming type to be
|
| + // a mutable array.
|
| if (input == inputs[1] && input.isStringOrArray()) {
|
| if (name == const SourceString('add')
|
| || name == const SourceString('removeLast')) {
|
| @@ -1243,10 +1302,8 @@ class HInvokeInterceptor extends HInvokeStatic {
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - bool hasExpectedType() => builtinJsName != null;
|
| -
|
| void prepareGvn() {
|
| - if (builtinJsName == 'length') {
|
| + if (isLengthGetterOnStringOrArray()) {
|
| clearAllSideEffects();
|
| } else {
|
| setAllSideEffects();
|
| @@ -1289,12 +1346,13 @@ class HFieldSet extends HInstruction {
|
|
|
| class HForeign extends HInstruction {
|
| final DartString code;
|
| - final DartString declaredType;
|
| - HForeign(this.code, this.declaredType, List<HInstruction> inputs)
|
| - : super(inputs);
|
| + final HType foreignType;
|
| + HForeign(this.code, DartString declaredType, List<HInstruction> inputs)
|
| + : foreignType = computeTypeFromDeclaredType(declaredType),
|
| + super(inputs);
|
| accept(HVisitor visitor) => visitor.visitForeign(this);
|
|
|
| - HType computeType() {
|
| + static HType computeTypeFromDeclaredType(DartString declaredType) {
|
| if (declaredType.slowToString() == 'bool') return HType.BOOLEAN;
|
| if (declaredType.slowToString() == 'int') return HType.INTEGER;
|
| if (declaredType.slowToString() == 'num') return HType.NUMBER;
|
| @@ -1302,7 +1360,7 @@ class HForeign extends HInstruction {
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - bool hasExpectedType() => true;
|
| + HType get guaranteedType() => foreignType;
|
| }
|
|
|
| class HForeignNew extends HForeign {
|
| @@ -1320,15 +1378,6 @@ class HInvokeBinary extends HInvokeStatic {
|
| HInstruction get left() => inputs[1];
|
| HInstruction get right() => inputs[2];
|
|
|
| - HType computeInputsType() {
|
| - HType leftType = left.type;
|
| - HType rightType = right.type;
|
| - if (leftType.isUnknown() || rightType.isUnknown()) {
|
| - return HType.UNKNOWN;
|
| - }
|
| - return leftType.combine(rightType);
|
| - }
|
| -
|
| abstract BinaryOperation get operation();
|
| }
|
|
|
| @@ -1350,22 +1399,33 @@ class HBinaryArithmetic extends HInvokeBinary {
|
|
|
| bool get builtin() => left.isNumber() && right.isNumber();
|
|
|
| - HType computeType() {
|
| - HType inputsType = computeInputsType();
|
| - if (inputsType.isKnown()) return inputsType;
|
| - if (left.isNumber()) return HType.NUMBER;
|
| + HType computeTypeFromInputTypes() {
|
| + if (left.isInteger() && right.isInteger()) return left.propagatedType;
|
| + if (left.isNumber()) {
|
| + if (left.isDouble() || right.isDouble()) return HType.DOUBLE;
|
| + return HType.NUMBER;
|
| + }
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - if (isNumber() || left.isNumber() || right.isNumber()) return HType.NUMBER;
|
| - if (type.isUnknown()) return HType.NUMBER;
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // If the desired output type should be an integer we want to get two
|
| + // integers as arguments.
|
| + if (propagatedType.isInteger()) return HType.INTEGER;
|
| + // If the outgoing type should be a number we can get that if both inputs
|
| + // are numbers. If we don't know the outgoing type we try to make it a
|
| + // number.
|
| + if (propagatedType.isUnknown() || propagatedType.isNumber()) {
|
| + return HType.NUMBER;
|
| + }
|
| + return HType.UNKNOWN;
|
| + }
|
| +
|
| + HType get likelyType() {
|
| + if (left.isTypeUnknown()) return HType.NUMBER;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - bool hasExpectedType() => left.isNumber() && right.isNumber();
|
| // TODO(1603): The class should be marked as abstract.
|
| abstract BinaryOperation get operation();
|
| }
|
| @@ -1381,24 +1441,38 @@ class HAdd extends HBinaryArithmetic {
|
| || (left.isString() && right is HConstant);
|
| }
|
|
|
| - HType computeType() {
|
| - HType computedType = computeInputsType();
|
| - if (computedType.isConflicting() && left.isString()) return HType.STRING;
|
| - if (computedType.isKnown()) return computedType;
|
| - if (left.isNumber()) return HType.NUMBER;
|
| + HType computeTypeFromInputTypes() {
|
| + if (left.isInteger() && right.isInteger()) return left.propagatedType;
|
| + if (left.isNumber()) {
|
| + if (left.isDouble() || right.isDouble()) return HType.DOUBLE;
|
| + return HType.NUMBER;
|
| + }
|
| + if (left.isString()) return HType.STRING;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - bool hasExpectedType() => builtin || type.isUnknown() || left.isString();
|
| -
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - if (isString() || left.isString()) {
|
| - return (input == left) ? HType.STRING : HType.UNKNOWN;
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // If the desired output type is an integer we want two integers as input.
|
| + if (propagatedType.isInteger()) {
|
| + return HType.INTEGER;
|
| }
|
| - if (right.isString()) return HType.STRING;
|
| - if (isNumber() || left.isNumber() || right.isNumber()) return HType.NUMBER;
|
| + // TODO(floitsch): remove string specialization once string+ is removed
|
| + // from dart2js.
|
| + if (propagatedType.isString() || left.isString() || right.isString()) {
|
| + return HType.STRING;
|
| + }
|
| + // If the desired output is a number or any of the inputs is a number
|
| + // ask for a number. Note that we might return the input's (say 'left')
|
| + // type depending on its (the 'left's) type. But that shouldn't matter.
|
| + if (propagatedType.isNumber() || left.isNumber() || right.isNumber()) {
|
| + return HType.NUMBER;
|
| + }
|
| + return HType.UNKNOWN;
|
| + }
|
| +
|
| + HType get likelyType() {
|
| + if (left.isString() || right.isString()) return HType.STRING;
|
| + if (left.isTypeUnknown() || left.isNumber()) return HType.NUMBER;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| @@ -1416,12 +1490,17 @@ class HDivide extends HBinaryArithmetic {
|
|
|
| bool get builtin() => left.isNumber() && right.isNumber();
|
|
|
| - HType computeType() {
|
| - HType inputsType = computeInputsType();
|
| + HType computeTypeFromInputTypes() {
|
| if (left.isNumber()) return HType.DOUBLE;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // A division can never return an integer. So don't ask for integer inputs.
|
| + if (propagatedType.isInteger()) return HType.UNKNOWN;
|
| + return super.computeDesiredTypeForNonTargetInput(input);
|
| + }
|
| +
|
| DivideOperation get operation() => const DivideOperation();
|
| int typeCode() => 6;
|
| bool typeEquals(other) => other is HDivide;
|
| @@ -1482,17 +1561,24 @@ class HBinaryBitOp extends HBinaryArithmetic {
|
|
|
| bool get builtin() => left.isInteger() && right.isInteger();
|
|
|
| - HType computeType() {
|
| - HType inputsType = computeInputsType();
|
| - if (inputsType.isKnown()) return inputsType;
|
| + HType computeTypeFromInputTypes() {
|
| if (left.isInteger()) return HType.INTEGER;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - return HType.INTEGER;
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // If the outgoing type should be a number we can get that only if both
|
| + // inputs are integers. If we don't know the outgoing type we try to make
|
| + // it an integer.
|
| + if (propagatedType.isUnknown() || propagatedType.isNumber()) {
|
| + return HType.INTEGER;
|
| + }
|
| + return HType.UNKNOWN;
|
| + }
|
| +
|
| + HType get likelyType() {
|
| + if (left.isTypeUnknown()) return HType.INTEGER;
|
| + return HType.UNKNOWN;
|
| }
|
|
|
| // TODO(floitsch): make class abstract instead of adding an abstract method.
|
| @@ -1574,20 +1660,22 @@ class HInvokeUnary extends HInvokeStatic {
|
|
|
| bool get builtin() => operand.isNumber();
|
|
|
| - HType computeType() {
|
| - HType operandType = operand.type;
|
| - if (!operandType.isUnknown()) return operandType;
|
| + HType computeTypeFromInputTypes() {
|
| + HType operandType = operand.propagatedType;
|
| + if (operandType.isNumber()) return operandType;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - if (type.isUnknown() || type.isNumber()) return HType.NUMBER;
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // If the outgoing type should be a number (integer, double or both) we
|
| + // want the outgoing type to be the input too.
|
| + // If we don't know the outgoing type we try to make it a number.
|
| + if (propagatedType.isNumber()) return propagatedType;
|
| + if (propagatedType.isUnknown()) return HType.NUMBER;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - bool hasExpectedType() => builtin || type.isUnknown();
|
| + HType get likelyType() => HType.NUMBER;
|
|
|
| abstract UnaryOperation get operation();
|
| }
|
| @@ -1608,16 +1696,19 @@ class HBitNot extends HInvokeUnary {
|
|
|
| bool get builtin() => operand.isInteger();
|
|
|
| - HType computeType() {
|
| - HType operandType = operand.type;
|
| - if (!operandType.isUnknown()) return operandType;
|
| + HType computeTypeFromInputTypes() {
|
| + HType operandType = operand.propagatedType;
|
| + if (operandType.isInteger()) return HType.INTEGER;
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - return HType.INTEGER;
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // Bit operations only work on integers. If there is no desired output
|
| + // type or if it as a number we want to get an integer as input.
|
| + if (propagatedType.isUnknown() || propagatedType.isNumber()) {
|
| + return HType.INTEGER;
|
| + }
|
| + return HType.UNKNOWN;
|
| }
|
|
|
| BitNotOperation get operation() => const BitNotOperation();
|
| @@ -1706,9 +1797,9 @@ class HLoopBranch extends HConditionalBranch {
|
|
|
| class HConstant extends HInstruction {
|
| final Constant constant;
|
| - HConstant.internal(this.constant, HType type) : super(<HInstruction>[]) {
|
| - this.type = type;
|
| - }
|
| + final HType constantType;
|
| + HConstant.internal(this.constant, HType this.constantType)
|
| + : super(<HInstruction>[]);
|
|
|
| void prepareGvn() {
|
| assert(!hasSideEffects());
|
| @@ -1716,9 +1807,8 @@ class HConstant extends HInstruction {
|
|
|
| toString() => 'literal: $constant';
|
| accept(HVisitor visitor) => visitor.visitConstant(this);
|
| - HType computeType() => type;
|
|
|
| - bool hasExpectedType() => true;
|
| + HType get guaranteedType() => constantType;
|
|
|
| bool isConstant() => true;
|
| bool isConstantBoolean() => constant.isBool();
|
| @@ -1737,11 +1827,10 @@ class HNot extends HInstruction {
|
| setUseGvn();
|
| }
|
|
|
| - HType computeType() => HType.BOOLEAN;
|
| - bool hasExpectedType() => true;
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - return HType.BOOLEAN;
|
| - }
|
| + HType get guaranteedType() => HType.BOOLEAN;
|
| +
|
| + // 'Not' only works on booleans. That's what we want as input.
|
| + HType computeDesiredTypeForInput(HInstruction input) => HType.BOOLEAN;
|
|
|
| accept(HVisitor visitor) => visitor.visitNot(this);
|
| int typeCode() => 18;
|
| @@ -1797,35 +1886,48 @@ class HPhi extends HInstruction {
|
| // have the same known type return it. If any two inputs have
|
| // different known types, we'll return a conflict -- otherwise we'll
|
| // simply return an unknown type.
|
| - HType computeInputsType() {
|
| + HType computeInputsType(bool unknownWins) {
|
| bool seenUnknown = false;
|
| - HType candidateType = inputs[0].type;
|
| + HType candidateType = inputs[0].propagatedType;
|
| for (int i = 1, length = inputs.length; i < length; i++) {
|
| - HType inputType = inputs[i].type;
|
| - if (inputType.isUnknown()) return HType.UNKNOWN;
|
| - candidateType = candidateType.combine(inputType);
|
| - if (candidateType.isConflicting()) return HType.CONFLICTING;
|
| + HType inputType = inputs[i].propagatedType;
|
| + if (inputType.isUnknown()) {
|
| + seenUnknown = true;
|
| + } else {
|
| + candidateType = candidateType.combine(inputType);
|
| + if (candidateType.isConflicting()) return HType.CONFLICTING;
|
| + }
|
| }
|
| + if (seenUnknown && unknownWins) return HType.UNKNOWN;
|
| return candidateType;
|
| }
|
|
|
| - HType computeType() {
|
| - HType inputsType = computeInputsType();
|
| - if (!inputsType.isUnknown()) return inputsType;
|
| - return super.computeType();
|
| + HType computeTypeFromInputTypes() {
|
| + HType inputsType = computeInputsType(true);
|
| + if (inputsType.isConflicting()) return HType.UNKNOWN;
|
| + return inputsType;
|
| }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - if (type.isNumber()) return HType.NUMBER;
|
| - if (type.isStringOrArray()) return HType.STRING_OR_ARRAY;
|
| - return type;
|
| + HType computeDesiredTypeForInput(HInstruction input) {
|
| + // Best case scenario for a phi is, when all inputs have the same type. If
|
| + // there is no desired outgoing type we therefore try to unify the input
|
| + // types (which is basically the [likelyType]).
|
| + if (propagatedType.isUnknown()) return likelyType;
|
| + // When the desired outgoing type is conflicting we don't need to give any
|
| + // requirements on the inputs.
|
| + if (propagatedType.isConflicting()) return HType.UNKNOWN;
|
| + // Otherwise the input type must match the desired outgoing type.
|
| + return propagatedType;
|
| }
|
|
|
| - bool hasExpectedType() {
|
| - for (int i = 0; i < inputs.length; i++) {
|
| - if (type.combine(inputs[i].type).isConflicting()) return false;
|
| - }
|
| - return true;
|
| + HType get likelyType() {
|
| + HType agreedType = computeInputsType(false);
|
| + if (agreedType.isConflicting()) return HType.UNKNOWN;
|
| + // Don't be too restrictive. If the agreed type is integer or double just
|
| + // say that the likely type is number. If more is expected the type will be
|
| + // propagated back.
|
| + if (agreedType.isNumber()) return HType.NUMBER;
|
| + return agreedType;
|
| }
|
|
|
| bool isLogicalOperator() => logicalOperatorType != IS_NOT_LOGICAL_OPERATOR;
|
| @@ -1843,9 +1945,7 @@ class HPhi extends HInstruction {
|
|
|
| class HRelational extends HInvokeBinary {
|
| HRelational(HStatic target, HInstruction left, HInstruction right)
|
| - : super(target, left, right) {
|
| - type = HType.BOOLEAN;
|
| - }
|
| + : super(target, left, right);
|
|
|
| void prepareGvn() {
|
| // Relational expressions can take part in global value numbering
|
| @@ -1859,19 +1959,24 @@ class HRelational extends HInvokeBinary {
|
| }
|
| }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - // For all relational operations exept HEquals, we expect to only
|
| - // get numbers.
|
| - return HType.NUMBER;
|
| + HType computeTypeFromInputTypes() {
|
| + if (left.isNumber()) return HType.BOOLEAN;
|
| + return HType.UNKNOWN;
|
| }
|
|
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + // For all relational operations exept HEquals, we expect to get numbers
|
| + // only. With numbers the outgoing type is a boolean. If something else
|
| + // is desired, then numbers are incorrect, though.
|
| + if (propagatedType.isUnknown() || propagatedType.isBoolean()) {
|
| + if (left.isTypeUnknown() || left.isNumber()) return HType.NUMBER;
|
| + }
|
| + return HType.UNKNOWN;
|
| + }
|
| +
|
| + HType get likelyType() => HType.BOOLEAN;
|
| +
|
| bool get builtin() => left.isNumber() && right.isNumber();
|
| - HType computeType() => HType.BOOLEAN;
|
| - // A HRelational goes through the builtin operator or the top level
|
| - // element. Therefore, it always has the expected type.
|
| - bool hasExpectedType() => true;
|
| // TODO(1603): the class should be marked as abstract.
|
| abstract BinaryOperation get operation();
|
| }
|
| @@ -1882,20 +1987,37 @@ class HEquals extends HRelational {
|
| accept(HVisitor visitor) => visitor.visitEquals(this);
|
|
|
| bool get builtin() {
|
| - if (left.isNumber() && right.isNumber()) return true;
|
| - if (left is !HConstant) return false;
|
| - HConstant leftConstant = left;
|
| - // TODO(floitsch): we can do better if we know that the constant does not
|
| - // have the equality operator overridden.
|
| - return !leftConstant.constant.isConstructedObject();
|
| + // All useful types have === semantics.
|
| + // Note that this includes all constants except the user-constructed
|
| + // objects.
|
| + return left.isConstantNull() || left.propagatedType.isUseful();
|
| }
|
|
|
| - HType computeType() => HType.BOOLEAN;
|
| + HType computeTypeFromInputTypes() {
|
| + if (builtin) return HType.BOOLEAN;
|
| + return HType.UNKNOWN;
|
| + }
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - if (left.isNumber() || right.isNumber()) return HType.NUMBER;
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + if (input == left && right.propagatedType.isUseful()) {
|
| + // All our useful types have === semantics. But we don't want to
|
| + // speculatively test for all possible types. Therefore we try to match
|
| + // the two types. That is, if we see x == 3, then we speculatively test
|
| + // if x is a number and bailout if it isn't.
|
| + if (right.isNumber()) return HType.NUMBER; // No need to be more precise.
|
| + // String equality testing is much more common than array equality
|
| + // testing.
|
| + if (right.isStringOrArray()) return HType.STRING;
|
| + return right.propagatedType;
|
| + }
|
| + // String equality testing is much more common than array equality testing.
|
| + if (input == left && left.isStringOrArray()) {
|
| + return HType.READABLE_ARRAY;
|
| + }
|
| + // String equality testing is much more common than array equality testing.
|
| + if (input == right && right.isStringOrArray()) {
|
| + return HType.STRING;
|
| + }
|
| return HType.UNKNOWN;
|
| }
|
|
|
| @@ -1911,10 +2033,11 @@ class HIdentity extends HRelational {
|
| accept(HVisitor visitor) => visitor.visitIdentity(this);
|
|
|
| bool get builtin() => true;
|
| - HType computeType() => HType.BOOLEAN;
|
| - bool hasExpectedType() => true;
|
|
|
| - HType computeDesiredInputType(HInstruction input) => HType.UNKNOWN;
|
| + HType get guaranteedType() => HType.BOOLEAN;
|
| + HType computeTypeFromInputTypes() => HType.BOOLEAN;
|
| + // Note that the identity operator really does not care for its input types.
|
| + HType computeDesiredTypeForInput(HInstruction input) => HType.UNKNOWN;
|
|
|
| IdentityOperation get operation() => const IdentityOperation();
|
| int typeCode() => 20;
|
| @@ -2014,8 +2137,8 @@ class HLiteralList extends HInstruction {
|
| HLiteralList(inputs) : super(inputs);
|
| toString() => 'literal list';
|
| accept(HVisitor visitor) => visitor.visitLiteralList(this);
|
| - HType computeType() => HType.MUTABLE_ARRAY;
|
| - bool hasExpectedType() => true;
|
| +
|
| + HType get guaranteedType() => HType.MUTABLE_ARRAY;
|
|
|
| void prepareGvn() {
|
| assert(!hasSideEffects());
|
| @@ -2039,16 +2162,18 @@ class HIndex extends HInvokeStatic {
|
| HInstruction get receiver() => inputs[1];
|
| HInstruction get index() => inputs[2];
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - if (input == receiver) return HType.STRING_OR_ARRAY;
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + if (input == receiver && (index.isTypeUnknown() || index.isNumber())) {
|
| + return HType.STRING_OR_ARRAY;
|
| + }
|
| + // The index should be an int when the receiver is a string or array.
|
| + // However it turns out that inserting an integer check in the optimized
|
| + // version is cheaper than having another bailout case. This is true,
|
| + // because the integer check will simply throw if it fails.
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - bool get builtin() => receiver.isStringOrArray();
|
| - HType computeType() => HType.UNKNOWN;
|
| - bool hasExpectedType() => false;
|
| + bool get builtin() => receiver.isStringOrArray() && index.isInteger();
|
| }
|
|
|
| class HIndexAssign extends HInvokeStatic {
|
| @@ -2065,18 +2190,21 @@ class HIndexAssign extends HInvokeStatic {
|
| HInstruction get index() => inputs[2];
|
| HInstruction get value() => inputs[3];
|
|
|
| - HType computeDesiredInputType(HInstruction input) {
|
| - // TODO(floitsch): we want the target to be a function.
|
| - if (input == target) return HType.UNKNOWN;
|
| - if (input == receiver) return HType.MUTABLE_ARRAY;
|
| + // Note, that we don't have a computeTypeFromInputTypes, since [HIndexAssign]
|
| + // is never used as input.
|
| +
|
| + HType computeDesiredTypeForNonTargetInput(HInstruction input) {
|
| + if (input == receiver && (index.isTypeUnknown() || index.isNumber())) {
|
| + return HType.MUTABLE_ARRAY;
|
| + }
|
| + // The index should be an int when the receiver is a string or array.
|
| + // However it turns out that inserting an integer check in the optimized
|
| + // version is cheaper than having another bailout case. This is true,
|
| + // because the integer check will simply throw if it fails.
|
| return HType.UNKNOWN;
|
| }
|
|
|
| - bool get builtin() => receiver.isMutableArray();
|
| - HType computeType() => value.type;
|
| - // This instruction does not yield a new value, so it always
|
| - // has the expected type (void).
|
| - bool hasExpectedType() => true;
|
| + bool get builtin() => receiver.isMutableArray() && index.isInteger();
|
| }
|
|
|
| class HIs extends HInstruction {
|
| @@ -2088,8 +2216,7 @@ class HIs extends HInstruction {
|
|
|
| HInstruction get expression() => inputs[0];
|
|
|
| - HType computeType() => HType.BOOLEAN;
|
| - bool hasExpectedType() => true;
|
| + HType get guaranteedType() => HType.BOOLEAN;
|
|
|
| accept(HVisitor visitor) => visitor.visitIs(this);
|
|
|
|
|