mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 23:21:43 +00:00
Refactor type propagation.
Review URL: https://chromiumcodereview.appspot.com//10098001 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@6599 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
310487fec0
commit
31e2a0b5ee
|
@ -276,7 +276,9 @@ class SsaTypeGuardBuilder extends HBaseVisitor implements OptimizationPhase {
|
|||
}
|
||||
|
||||
bool shouldCaptureEnvironment(HInstruction instruction) {
|
||||
return instruction.type.isKnown() && !instruction.hasExpectedType();
|
||||
HType propagatedType = instruction.propagatedType;
|
||||
return propagatedType.isUseful()
|
||||
&& propagatedType != instruction.computeTypeFromInputTypes();
|
||||
}
|
||||
|
||||
void insertCapturedEnvironments() {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -54,8 +54,8 @@ class SsaOptimizerTask extends CompilerTask {
|
|||
// propagate types from the instruction to the type guard. We do it
|
||||
// now to be able to optimize further.
|
||||
work.guards.forEach((HTypeGuard guard) {
|
||||
guard.type = guard.guarded.type;
|
||||
guard.guarded.type = HType.UNKNOWN;
|
||||
guard.propagatedType = guard.guarded.propagatedType;
|
||||
guard.guarded.propagatedType = HType.UNKNOWN;
|
||||
});
|
||||
// We also need to insert range and integer checks for the type guards,
|
||||
// now that they know their type. We did not need to do that
|
||||
|
@ -99,8 +99,8 @@ class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
|
|||
block.remove(instruction);
|
||||
// If the replacement instruction does not know its type yet,
|
||||
// use the type of the instruction.
|
||||
if (!replacement.type.isKnown()) {
|
||||
replacement.type = instruction.type;
|
||||
if (!replacement.propagatedType.isUseful()) {
|
||||
replacement.propagatedType = instruction.propagatedType;
|
||||
}
|
||||
}
|
||||
instruction = next;
|
||||
|
@ -117,7 +117,7 @@ class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
|
|||
HInstruction input = inputs[0];
|
||||
if (input.isBoolean()) return input;
|
||||
// All values !== true are boolified to false.
|
||||
if (input.type.isKnown()) {
|
||||
if (input.propagatedType.isUseful()) {
|
||||
return graph.addConstantBool(false);
|
||||
}
|
||||
return node;
|
||||
|
@ -186,7 +186,8 @@ class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
|
|||
|
||||
HInstruction visitTypeGuard(HTypeGuard node) {
|
||||
HInstruction value = node.guarded;
|
||||
return (value.type.combine(node.type) == value.type) ? value : node;
|
||||
HType combinedType = value.propagatedType.combine(node.propagatedType);
|
||||
return (combinedType == value.propagatedType) ? value : node;
|
||||
}
|
||||
|
||||
HInstruction visitIntegerCheck(HIntegerCheck node) {
|
||||
|
@ -201,7 +202,7 @@ class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
|
|||
compiler.unimplemented("visitIs for type variables");
|
||||
}
|
||||
|
||||
HType expressionType = node.expression.type;
|
||||
HType expressionType = node.expression.propagatedType;
|
||||
if (element === compiler.objectClass
|
||||
|| element === compiler.dynamicClass) {
|
||||
return graph.addConstantBool(true);
|
||||
|
@ -284,7 +285,7 @@ class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
|
|||
const SourceString("length"),
|
||||
true,
|
||||
<HInstruction>[interceptor, receiver]);
|
||||
length.type = HType.NUMBER;
|
||||
length.propagatedType = HType.INTEGER;
|
||||
node.block.addBefore(node, length);
|
||||
|
||||
HBoundsCheck check = new HBoundsCheck(length, index);
|
||||
|
@ -299,9 +300,12 @@ class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
|
|||
}
|
||||
|
||||
void visitIndex(HIndex node) {
|
||||
if (!node.builtin) return;
|
||||
if (node.index is HBoundsCheck) return;
|
||||
HInstruction index = insertIntegerCheck(node, node.index);
|
||||
if (!node.receiver.isStringOrArray()) return;
|
||||
HInstruction index = node.index;
|
||||
if (index is HBoundsCheck) return;
|
||||
if (!node.index.isInteger()) {
|
||||
index = insertIntegerCheck(node, index);
|
||||
}
|
||||
index = insertBoundsCheck(node, node.receiver, index);
|
||||
HIndex newInstruction = new HIndex(node.target, node.receiver, index);
|
||||
node.block.addBefore(node, newInstruction);
|
||||
|
@ -310,9 +314,12 @@ class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
|
|||
}
|
||||
|
||||
void visitIndexAssign(HIndexAssign node) {
|
||||
if (!node.builtin) return;
|
||||
if (node.index is HBoundsCheck) return;
|
||||
HInstruction index = insertIntegerCheck(node, node.index);
|
||||
if (!node.receiver.isMutableArray()) return;
|
||||
HInstruction index = node.index;
|
||||
if (index is HBoundsCheck) return;
|
||||
if (!node.index.isInteger()) {
|
||||
index = insertIntegerCheck(node, index);
|
||||
}
|
||||
index = insertBoundsCheck(node, node.receiver, index);
|
||||
HIndexAssign newInstruction =
|
||||
new HIndexAssign(node.target, node.receiver, index, node.value);
|
||||
|
|
|
@ -165,7 +165,8 @@ class HInstructionStringifier implements HVisitor<String> {
|
|||
|
||||
String temporaryId(HInstruction instruction) {
|
||||
String prefix;
|
||||
switch (instruction.type) {
|
||||
HType type = instruction.propagatedType;
|
||||
switch (type) {
|
||||
case HType.MUTABLE_ARRAY: prefix = 'a'; break;
|
||||
case HType.READABLE_ARRAY: prefix = 'roa'; break;
|
||||
case HType.BOOLEAN: prefix = 'b'; break;
|
||||
|
@ -402,7 +403,7 @@ class HInstructionStringifier implements HVisitor<String> {
|
|||
|
||||
String visitTypeGuard(HTypeGuard node) {
|
||||
String type;
|
||||
switch (node.type) {
|
||||
switch (node.propagatedType) {
|
||||
case HType.MUTABLE_ARRAY: type = "mutable_array"; break;
|
||||
case HType.READABLE_ARRAY: type = "readable_array"; break;
|
||||
case HType.BOOLEAN: type = "bool"; break;
|
||||
|
|
|
@ -14,19 +14,21 @@ class SsaTypePropagator extends HGraphVisitor implements OptimizationPhase {
|
|||
worklist = new List<int>();
|
||||
|
||||
|
||||
HType computeType(HInstruction instruction) => instruction.computeType();
|
||||
HType computeType(HInstruction instruction) {
|
||||
return instruction.computeTypeFromInputTypes();
|
||||
}
|
||||
|
||||
// Re-compute and update the type of the instruction. Returns
|
||||
// whether or not the type was changed.
|
||||
bool updateType(HInstruction instruction) {
|
||||
if (instruction.type.isConflicting()) return false;
|
||||
// Constants have the type they have. It can't be changed.
|
||||
if (instruction.isConstant()) return false;
|
||||
if (instruction.propagatedType.isConflicting()) return false;
|
||||
|
||||
HType oldType = instruction.type;
|
||||
HType newType = computeType(instruction);
|
||||
instruction.type = oldType.combine(newType);
|
||||
return oldType !== instruction.type;
|
||||
HType oldType = instruction.propagatedType;
|
||||
HType newType = instruction.hasGuaranteedType()
|
||||
? instruction.guaranteedType
|
||||
: computeType(instruction);
|
||||
instruction.propagatedType = oldType.combine(newType);
|
||||
return oldType !== instruction.propagatedType;
|
||||
}
|
||||
|
||||
void visitGraph(HGraph graph) {
|
||||
|
@ -37,19 +39,24 @@ class SsaTypePropagator extends HGraphVisitor implements OptimizationPhase {
|
|||
visitBasicBlock(HBasicBlock block) {
|
||||
if (block.isLoopHeader()) {
|
||||
block.forEachPhi((HPhi phi) {
|
||||
// Set the initial type for the phi.
|
||||
phi.type = phi.inputs[0].type;
|
||||
// Set the initial type for the phi. In theory we would need to mark the
|
||||
// type of all other incoming edges as "unitialized" and take this into
|
||||
// account when doing the propagation inside the phis. Just setting
|
||||
// the [propagatedType] is however easier.
|
||||
phi.propagatedType = phi.inputs[0].propagatedType;
|
||||
addToWorkList(phi);
|
||||
});
|
||||
} else {
|
||||
block.forEachPhi((HPhi phi) {
|
||||
if (updateType(phi)) addUsersAndInputsToWorklist(phi);
|
||||
if (updateType(phi)) addDependentInstructionsToWorkList(phi);
|
||||
});
|
||||
}
|
||||
|
||||
HInstruction instruction = block.first;
|
||||
while (instruction !== null) {
|
||||
if (updateType(instruction)) addUsersAndInputsToWorklist(instruction);
|
||||
if (updateType(instruction)) {
|
||||
addDependentInstructionsToWorkList(instruction);
|
||||
}
|
||||
instruction = instruction.next;
|
||||
}
|
||||
}
|
||||
|
@ -60,17 +67,18 @@ class SsaTypePropagator extends HGraphVisitor implements OptimizationPhase {
|
|||
HInstruction instruction = workmap[id];
|
||||
assert(instruction !== null);
|
||||
workmap.remove(id);
|
||||
if (updateType(instruction)) addUsersAndInputsToWorklist(instruction);
|
||||
if (updateType(instruction)) {
|
||||
addDependentInstructionsToWorkList(instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addUsersAndInputsToWorklist(HInstruction instruction) {
|
||||
void addDependentInstructionsToWorkList(HInstruction instruction) {
|
||||
for (int i = 0, length = instruction.usedBy.length; i < length; i++) {
|
||||
// The non-speculative type propagator only propagates types forward. We
|
||||
// thus only need to add the users of the [instruction] to the list.
|
||||
addToWorkList(instruction.usedBy[i]);
|
||||
}
|
||||
for (int i = 0, length = instruction.inputs.length; i < length; i++) {
|
||||
addToWorkList(instruction.inputs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToWorkList(HInstruction instruction) {
|
||||
|
@ -86,11 +94,24 @@ class SsaSpeculativeTypePropagator extends SsaTypePropagator {
|
|||
final String name = 'speculative type propagator';
|
||||
SsaSpeculativeTypePropagator(Compiler compiler) : super(compiler);
|
||||
|
||||
void addDependentInstructionsToWorkList(HInstruction instruction) {
|
||||
// The speculative type propagator propagates types forward and backward.
|
||||
// Not only do we need to add the users of the [instruction] to the list.
|
||||
// We also need to add the inputs fo the [instruction], since they might
|
||||
// want to propagate the desired outgoing type.
|
||||
for (int i = 0, length = instruction.usedBy.length; i < length; i++) {
|
||||
addToWorkList(instruction.usedBy[i]);
|
||||
}
|
||||
for (int i = 0, length = instruction.inputs.length; i < length; i++) {
|
||||
addToWorkList(instruction.inputs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
HType computeDesiredType(HInstruction instruction) {
|
||||
HType desiredType = HType.UNKNOWN;
|
||||
for (final user in instruction.usedBy) {
|
||||
desiredType =
|
||||
desiredType.combine(user.computeDesiredInputType(instruction));
|
||||
desiredType.combine(user.computeDesiredTypeForInput(instruction));
|
||||
// No need to continue if two users disagree on the type.
|
||||
if (desiredType.isConflicting()) break;
|
||||
}
|
||||
|
@ -99,9 +120,12 @@ class SsaSpeculativeTypePropagator extends SsaTypePropagator {
|
|||
|
||||
HType computeType(HInstruction instruction) {
|
||||
HType newType = super.computeType(instruction);
|
||||
// [computeDesiredType] goes to all usedBys and lets them compute their
|
||||
// desired type. By setting the [newType] here we give them more context to
|
||||
// work with.
|
||||
instruction.propagatedType = newType;
|
||||
HType desiredType = computeDesiredType(instruction);
|
||||
// If the desired type is conflicting just return the computed
|
||||
// type.
|
||||
// If the desired type is conflicting just return the computed type.
|
||||
if (desiredType.isConflicting()) return newType;
|
||||
return newType.combine(desiredType);
|
||||
}
|
||||
|
|
|
@ -61,12 +61,11 @@ GenericDeepTest: Fail # Expect.isTrue(false) fails.
|
|||
GenericInheritanceTest: Fail # Expect.isTrue(false) fails.
|
||||
GenericParameterizedExtendsTest: Fail # Expect.isTrue(false) fails.
|
||||
Instanceof2Test: Fail # Expect.equals(expected: <true>, actual: <false>) fails.
|
||||
ListDoubleIndexInLoopTest: Fail # Issue 2564.
|
||||
ListDoubleIndexInLoop2Test: Fail # Issue 2564.
|
||||
ListLiteral4Test: Fail # Illegal argument(s): 0 -- checked mode test.
|
||||
MapLiteral4Test: Fail # Attempt to modify an immutable object -- checked mode test.
|
||||
NamedParametersTypeTest: Fail # Expect.equals(expected: <111>, actual: <0>) fails. -- checked mode test.
|
||||
Operator3Test: Fail # Issue 2558.
|
||||
Operator4Test: Fail # Issue 2563.
|
||||
TypeChecksInFactoryMethodTest: Fail # Expect.equals(expected: <true>, actual: <false>) fails. -- checked mode test.
|
||||
TypeDartcTest: Fail # Expect.equals(expected: <1>, actual: <0>) -- checked mode test.
|
||||
|
||||
|
|
33
tests/language/src/ListDoubleIndexInLoop2Test.dart
Normal file
33
tests/language/src/ListDoubleIndexInLoop2Test.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2012, 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.
|
||||
// Dart test program for testing arrays.
|
||||
|
||||
bar() => true;
|
||||
|
||||
tata() => 1.5;
|
||||
|
||||
// The type propagation in Dart2Js wrongly took the intersection of all incoming
|
||||
// types in a loop-phi. In this case the back-edge brought type 'number' which,
|
||||
// combined with 'integer' (i = 0) was narrowed to 'integer'. As a result no
|
||||
// check was inserted for the list access.
|
||||
foo(a) {
|
||||
var i;
|
||||
if (bar()) {
|
||||
// t's desired type is conflicting. Once it is used as array receiver. And
|
||||
// once as integer. The backward propagation thus can't decide.
|
||||
// The forward declaration, however, will assign type num.
|
||||
var t = 0 + tata();
|
||||
i = t;
|
||||
if (!bar()) t[0];
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
// The phi, combining the two 'i's must reach the conclusion that i is of
|
||||
// type num and therefore needs a check before accessing the array.
|
||||
a[i];
|
||||
}
|
||||
|
||||
main() {
|
||||
Expect.throws(() => foo([1, 2]));
|
||||
}
|
Loading…
Reference in a new issue