Chromium Code Reviews| Index: lib/compiler/implementation/ssa/nodes.dart |
| diff --git a/lib/compiler/implementation/ssa/nodes.dart b/lib/compiler/implementation/ssa/nodes.dart |
| index 19f794894f0250e7588c7940155c9b81fbd85197..9c7cae8321430572afc8a468255a7ca6ce29765c 100644 |
| --- a/lib/compiler/implementation/ssa/nodes.dart |
| +++ b/lib/compiler/implementation/ssa/nodes.dart |
| @@ -667,88 +667,126 @@ class HBasicBlock extends HInstructionList implements Hashable { |
| class HType { |
| - final int flag; |
| - const HType(int this.flag); |
| - |
| - static final int FLAG_CONFLICTING = 0; |
| - static final int FLAG_UNKNOWN = 1; |
| - static final int FLAG_BOOLEAN = FLAG_UNKNOWN << 1; |
| - static final int FLAG_INTEGER = FLAG_BOOLEAN << 1; |
| - static final int FLAG_STRING = FLAG_INTEGER << 1; |
| - static final int FLAG_READABLE_ARRAY = FLAG_STRING << 1; |
| - // FLAG_WRITABLE_ARRAY implies FLAG_READABLE_ARRAY. |
| - static final int FLAG_WRITEABLE_ARRAY = FLAG_READABLE_ARRAY << 1; |
| - static final int FLAG_DOUBLE = FLAG_WRITEABLE_ARRAY << 1; |
| - static final int FLAG_NON_PRIMITIVE = FLAG_DOUBLE << 1; |
| - |
| - static final HType CONFLICTING = const HType(FLAG_CONFLICTING); |
| - static final HType UNKNOWN = const HType(FLAG_UNKNOWN); |
| - static final HType BOOLEAN = const HType(FLAG_BOOLEAN); |
| - static final HType STRING = const HType(FLAG_STRING); |
| - static final HType READABLE_ARRAY = const HType(FLAG_READABLE_ARRAY); |
| - static final HType MUTABLE_ARRAY = |
| - const HType(FLAG_READABLE_ARRAY | FLAG_WRITEABLE_ARRAY); |
| - static final HType INTEGER = const HType(FLAG_INTEGER); |
| - static final HType DOUBLE = const HType(FLAG_DOUBLE); |
| - static final HType STRING_OR_ARRAY = |
| - const HType(FLAG_STRING | FLAG_READABLE_ARRAY | FLAG_WRITEABLE_ARRAY); |
| - static final HType NUMBER = const HType(FLAG_DOUBLE | FLAG_INTEGER); |
| + // We need a field so that the different static types are not canonicalized. |
| + // This way we don't need to do special work for the toString. |
| + final String typeName; |
| + const HType(this.typeName); |
| + |
| + static final HType CONFLICTING = const HType("conflicting"); |
| + static final HType UNKNOWN = const HType("unknown"); |
| + static final HType BOOLEAN = const HType("boolean"); |
| + static final HType STRING = const HType("string"); |
| + static final HType READABLE_ARRAY = const HType("readable array"); |
| + // Note that mutable array is considered to be a subtype of readable array. |
| + // That is, all values of type mutable array are also readable arrays, but not |
| + // the inverse. |
| + static final HType MUTABLE_ARRAY = const HType("mutable array"); |
| + static final HType INTEGER = const HType("integer"); |
| + static final HType DOUBLE = const HType("double"); |
| + static final HType INTEGER_OR_DOUBLE = const HType("integer or double"); |
| + // In this context "Array" means readable array. |
| + static final HType STRING_OR_ARRAY = const HType("string or array"); |
| bool isConflicting() => this === CONFLICTING; |
| bool isUnknown() => this === UNKNOWN; |
| bool isBoolean() => this === BOOLEAN; |
| bool isInteger() => this === INTEGER; |
| bool isDouble() => this === DOUBLE; |
| + bool isIntegerOrDouble() => this === INTEGER_OR_DOUBLE; |
|
kasperl
2012/04/23 05:48:30
Not sure if should fix this but it is pretty weird
floitsch
2012/04/23 09:21:05
Nicolas is complaining too, but that was the reaso
|
| bool isString() => this === STRING; |
| - bool isArray() => (this.flag & FLAG_READABLE_ARRAY) != 0; |
| + bool isReadableArray() => this === READABLE_ARRAY; |
| bool isMutableArray() => this === MUTABLE_ARRAY; |
| - bool isNumber() => (this.flag & (FLAG_INTEGER | FLAG_DOUBLE)) != 0; |
| - bool isStringOrArray() => |
| - (this.flag & (FLAG_STRING | FLAG_READABLE_ARRAY)) != 0; |
| - bool isNonPrimitive() => this.flag === FLAG_NON_PRIMITIVE; |
| + bool isStringOrArray() => this === STRING_OR_ARRAY; |
| + bool isNonPrimitive() => false; |
| + bool isWithoutSubtype() { |
|
kasperl
2012/04/23 05:48:30
hasNoSubtypes?
floitsch
2012/04/23 09:21:05
Done.
|
| + return isInteger() || isDouble() || isString() || isMutableArray(); |
| + } |
| + bool isNumber() => isInteger() || isDouble() || isIntegerOrDouble(); |
| + /** [isArray] does not include [isStringOrArray]. */ |
| + bool isArray() => isReadableArray() || isMutableArray(); |
| + bool isIndexablePrimitive() => isString() || isArray() || isStringOrArray(); |
| /** 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; |
| - if (flag === UNKNOWN.flag) return UNKNOWN; |
| - if (flag === BOOLEAN.flag) return BOOLEAN; |
| - if (flag === INTEGER.flag) return INTEGER; |
| - if (flag === DOUBLE.flag) return DOUBLE; |
| - if (flag === STRING.flag) return STRING; |
| - if (flag === READABLE_ARRAY.flag) return READABLE_ARRAY; |
| - if (flag === MUTABLE_ARRAY.flag) return MUTABLE_ARRAY; |
| - if (flag === NUMBER.flag) return NUMBER; |
| - if (flag === STRING_OR_ARRAY.flag) return STRING_OR_ARRAY; |
| - unreachable(); |
| - } |
| + bool isUseful() => !isUnknown() && !isConflicting(); |
| - String toString() { |
| - if (isConflicting()) return 'conflicting'; |
| - if (isUnknown()) return 'unknown'; |
| - if (isBoolean()) return 'boolean'; |
| - if (isInteger()) return 'integer'; |
| - if (isDouble()) return 'double'; |
| - if (isString()) return 'string'; |
| - if (isMutableArray()) return 'mutable array'; |
| - if (isArray()) return 'array'; |
| - if (isNumber()) return 'number'; |
| - if (isStringOrArray()) return 'string or array'; |
| - unreachable(); |
| + String toString() => typeName; |
| + |
| + /** |
| + * The intersection of two types is the intersection of its values. For |
| + * example: |
| + * * int.intersect(num) => int. |
| + * * double.intersect(int) => conflict. |
| + * * mutableArray.intersect(array) => mutableArray. |
| + * |
| + * When there is no predefined type to represent the intersection returns |
| + * [CONFLICTING]. |
| + */ |
| + HType intersect(HType other) { |
| + if (this === other) return this; |
| + // unknown has no influence on the type. Always just return the other type. |
|
kasperl
2012/04/23 05:48:30
unknown -> Unknown
floitsch
2012/04/23 09:21:05
Done.
|
| + if (isUnknown()) return other; |
| + if (other.isUnknown()) return this; |
| + |
| + // Avoid testing some of the combinations by making the concreter type |
|
kasperl
2012/04/23 05:48:30
concreter -> most concrete
floitsch
2012/04/23 09:21:05
Done.
|
| + // [:this:]. |
| + if (!isWithoutSubtype() && other.isWithoutSubtype()) { |
| + return other.intersect(this); |
| + } |
| + |
| + // If we other is a super-type return [this]. |
|
kasperl
2012/04/23 05:48:30
we other?
floitsch
2012/04/23 09:21:05
Done.
|
| + if (isInteger() && other.isIntegerOrDouble()) return this; |
| + if (isDouble() && other.isIntegerOrDouble()) return this; |
| + if (isString() && other.isStringOrArray()) return this; |
| + if (isMutableArray() && other.isIndexablePrimitive()) return this; |
| + |
| + // Handle the cases where neither side is a supertype of the other. |
| + if (isArray() && other.isStringOrArray()) return this; |
| + if (other.isArray() && isStringOrArray()) return other; |
| + |
| + return CONFLICTING; |
| } |
| - HType combine(HType other) { |
| + /** |
| + * The union of two types is the union of its values. For example: |
| + * * int.union(num) => num. |
|
kasperl
2012/04/23 05:48:30
INTEGER.union(NUMBER) => NUMBER?
floitsch
2012/04/23 09:21:05
Done.
|
| + * * double.union(int) => num. |
| + * * mutableArray.union(array) => array. |
|
kasperl
2012/04/23 05:48:30
The casing makes it look like mutableArray is some
floitsch
2012/04/23 09:21:05
Changed to capital case.
|
| + * |
| + * When there is no predefined type to represent the union returns |
| + * [CONFLICTING]. |
| + */ |
| + HType union(HType other) { |
| + if (this === other) return this; |
| + // unknown has no influence on the type. Always just return the other type. |
|
kasperl
2012/04/23 05:48:30
u -> U
floitsch
2012/04/23 09:21:05
Done.
|
| if (isUnknown()) return other; |
| if (other.isUnknown()) return this; |
| - return getTypeFromFlag(this.flag & other.flag); |
| + |
| + // Avoid testing some of the combinations by making the concreter type |
|
kasperl
2012/04/23 05:48:30
concreter -> most concrete
floitsch
2012/04/23 09:21:05
Done.
|
| + // [:this:]. |
| + if (!isWithoutSubtype() && other.isWithoutSubtype()) { |
| + return other.union(this); |
| + } |
| + |
| + // If [other] is a super type of [this] return [other]; |
| + if (isNumber() && other.isIntegerOrDouble()) return other; |
| + if (isArray() && other.isReadableArray()) return other; |
| + if ((isString() || isArray()) && other.isStringOrArray()) return other; |
| + |
| + // Handle the cases where neither side is a super type of the other. |
| + if (isNumber() && other.isNumber()) return INTEGER_OR_DOUBLE; |
| + if (isString() && other.isArray()) return STRING_OR_ARRAY; |
| + if (isArray() && other.isString()) return STRING_OR_ARRAY; |
| + |
| + return CONFLICTING; |
| } |
| } |
| class HNonPrimitiveType extends HType { |
| final Type type; |
| - // TODO(ngeoffray): Add a HPrimitiveType to get rid of the flag. |
| - const HNonPrimitiveType(Type this.type) : super(HType.FLAG_NON_PRIMITIVE); |
| + // TODO(ngeoffray): Add a HPrimitiveType to get rid of the string. |
| + const HNonPrimitiveType(Type this.type) : super("non primitive"); |
| + |
| + bool isNonPrimitive() => true; |
| HType combine(HType other) { |
| if (other.isNonPrimitive()) { |
| @@ -759,6 +797,11 @@ class HNonPrimitiveType extends HType { |
| return HType.CONFLICTING; |
| } |
| + // As long as we don't keep track of super/sub types for non-primitive types |
| + // the intersection and union is the same. |
| + HType intersect(HType other) => combine(other); |
| + HType union(HType other) => combine(other); |
| + |
| String toString() => type.toString(); |
| Element lookupMember(SourceString name) { |
| ClassElement classElement = type.element; |
| @@ -818,14 +861,17 @@ class HInstruction implements Hashable { |
| // All isFunctions work on the propagated types. |
| bool isArray() => propagatedType.isArray(); |
| + bool isReadableArray() => propagatedType.isReadableArray(); |
| bool isMutableArray() => propagatedType.isMutableArray(); |
| bool isBoolean() => propagatedType.isBoolean(); |
| bool isInteger() => propagatedType.isInteger(); |
| bool isDouble() => propagatedType.isDouble(); |
| + bool isIntegerOrDouble() => propagatedType.isIntegerOrDouble(); |
| bool isNumber() => propagatedType.isNumber(); |
| bool isString() => propagatedType.isString(); |
| bool isTypeUnknown() => propagatedType.isUnknown(); |
| bool isStringOrArray() => propagatedType.isStringOrArray(); |
| + bool isIndexablePrimitive() => propagatedType.isIndexablePrimitive(); |
| bool isNonPrimitive() => propagatedType.isNonPrimitive(); |
| /** |
| @@ -1203,8 +1249,7 @@ class HInvokeInterceptor extends HInvokeStatic { |
| } |
| bool isLengthGetterOnStringOrArray() { |
| - return isLengthGetter() |
| - && inputs[1].isStringOrArray(); |
| + return isLengthGetter() && inputs[1].isIndexablePrimitive(); |
| } |
| String get builtinJsName() { |
| @@ -1237,7 +1282,7 @@ class HInvokeInterceptor extends HInvokeStatic { |
| // 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 (input == inputs[1] && input.isIndexablePrimitive()) { |
| if (name == const SourceString('add') |
| || name == const SourceString('removeLast')) { |
| return HType.MUTABLE_ARRAY; |
| @@ -1299,7 +1344,8 @@ class HForeign extends HInstruction { |
| 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; |
| + if (declaredType.slowToString() == 'double') return HType.DOUBLE; |
| + if (declaredType.slowToString() == 'num') return HType.INTEGER_OR_DOUBLE; |
| if (declaredType.slowToString() == 'String') return HType.STRING; |
| return HType.UNKNOWN; |
| } |
| @@ -1348,7 +1394,7 @@ class HBinaryArithmetic extends HInvokeBinary { |
| if (left.isInteger() && right.isInteger()) return left.propagatedType; |
| if (left.isNumber()) { |
| if (left.isDouble() || right.isDouble()) return HType.DOUBLE; |
| - return HType.NUMBER; |
| + return HType.INTEGER_OR_DOUBLE; |
| } |
| return HType.UNKNOWN; |
| } |
| @@ -1361,7 +1407,7 @@ class HBinaryArithmetic extends HInvokeBinary { |
| // 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.INTEGER_OR_DOUBLE; |
| } |
| // Even if the desired outgoing type is not a number we still want the |
| // second argument to be a number if the first one is a number. This will |
| @@ -1370,12 +1416,12 @@ class HBinaryArithmetic extends HInvokeBinary { |
| // TODO(floitsch): normally we shouldn't request a number, but simply |
| // throw an IllegalArgumentException if it isn't. This would be similar |
| // to the array case. |
| - if (input == right && left.isNumber()) return HType.NUMBER; |
| + if (input == right && left.isNumber()) return HType.INTEGER_OR_DOUBLE; |
| return HType.UNKNOWN; |
| } |
| HType get likelyType() { |
| - if (left.isTypeUnknown()) return HType.NUMBER; |
| + if (left.isTypeUnknown()) return HType.INTEGER_OR_DOUBLE; |
| return HType.UNKNOWN; |
| } |
| @@ -1398,7 +1444,7 @@ class HAdd extends HBinaryArithmetic { |
| if (left.isInteger() && right.isInteger()) return left.propagatedType; |
| if (left.isNumber()) { |
| if (left.isDouble() || right.isDouble()) return HType.DOUBLE; |
| - return HType.NUMBER; |
| + return HType.INTEGER_OR_DOUBLE; |
| } |
| if (left.isString()) return HType.STRING; |
| return HType.UNKNOWN; |
| @@ -1418,14 +1464,14 @@ class HAdd extends HBinaryArithmetic { |
| // 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.INTEGER_OR_DOUBLE; |
| } |
| return HType.UNKNOWN; |
| } |
| HType get likelyType() { |
| if (left.isString() || right.isString()) return HType.STRING; |
| - if (left.isTypeUnknown() || left.isNumber()) return HType.NUMBER; |
| + if (left.isTypeUnknown() || left.isNumber()) return HType.INTEGER_OR_DOUBLE; |
| return HType.UNKNOWN; |
| } |
| @@ -1624,11 +1670,11 @@ class HInvokeUnary extends HInvokeStatic { |
| // 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; |
| + if (propagatedType.isUnknown()) return HType.INTEGER_OR_DOUBLE; |
| return HType.UNKNOWN; |
| } |
| - HType get likelyType() => HType.NUMBER; |
| + HType get likelyType() => HType.INTEGER_OR_DOUBLE; |
| abstract UnaryOperation get operation(); |
| } |
| @@ -1849,7 +1895,7 @@ class HPhi extends HInstruction { |
| if (inputType.isUnknown()) { |
| seenUnknown = true; |
| } else { |
| - candidateType = candidateType.combine(inputType); |
| + candidateType = candidateType.union(inputType); |
| if (candidateType.isConflicting()) return HType.CONFLICTING; |
| } |
| } |
| @@ -1881,7 +1927,7 @@ class HPhi extends HInstruction { |
| // 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; |
| + if (agreedType.isNumber()) return HType.INTEGER_OR_DOUBLE; |
| return agreedType; |
| } |
| @@ -1924,7 +1970,9 @@ class HRelational extends HInvokeBinary { |
| // 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; |
| + if (left.isTypeUnknown() || left.isNumber()) { |
| + return HType.INTEGER_OR_DOUBLE; |
| + } |
| } |
| return HType.UNKNOWN; |
| } |
| @@ -1959,7 +2007,9 @@ class HEquals extends HRelational { |
| // 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. |
| + // If right is a number we don't need more than a number (no need to match |
| + // the exact type of right). |
| + if (right.isNumber()) return HType.INTEGER_OR_DOUBLE; |
| // String equality testing is much more common than array equality |
| // testing. |
| if (right.isStringOrArray()) return HType.STRING; |
| @@ -2128,7 +2178,7 @@ class HIndex extends HInvokeStatic { |
| return HType.UNKNOWN; |
| } |
| - bool get builtin() => receiver.isStringOrArray() && index.isInteger(); |
| + bool get builtin() => receiver.isIndexablePrimitive() && index.isInteger(); |
| } |
| class HIndexAssign extends HInvokeStatic { |