dart2js cps: More aggressive operator specialization.

Inline more unsafe operator calls, and insert more checks accordingly.

When "num" is the only receiver that does not respond with NSM, insert
a check and use the builtin operator.  Previously we only did this when
the receiver was a number or null. For example:

  if (typeof x !== "number") return x.$lt();
  result = x < 4;

When the argument might have the wrong type, insert an argument check
and use the builtin operator.  Previously we never did this if the
argument might have the wrong type. For example:

  if (typeof x !== "number") return H.iae(x);
  result = 4 < x;

If both receiver and argument need checks, we join the two checks and
use a one-shot interceptor to throw the error:

  if (typeof x !== "number" || typeof y !== "number")
    return J.$lt(x, y);
  result = x < y;

NullCheck has been generalized to a ReceiverCheck instruction to
support "x.$lt()" above, that throws even if x is a non-null value.

BUG=
R=sigmund@google.com

Review URL: https://codereview.chromium.org/1668913002 .
This commit is contained in:
Asger Feldthaus
2016-02-09 13:34:37 +01:00
parent a0d7b64fde
commit bbca76a5e2
44 changed files with 516 additions and 181 deletions
@@ -19,11 +19,11 @@ import 'cps_fragment.dart';
///
/// print(x.length);
///
/// `x.length` will throw when x is null, so the original [NullCheck] is not
/// `x.length` will throw when x is null, so the original [ReceiverCheck] is not
/// needed. This changes the error message, but at least for now we are
/// willing to accept this.
///
/// Note that code motion may not occur after this pass, since the [NullCheck]
/// Note that code motion may not occur after this pass, since the [ReceiverCheck]
/// nodes are not there to restrict it.
//
// TODO(asgerf): It would be nice with a clear specification of when we allow
@@ -53,7 +53,7 @@ class BackwardNullCheckRemover extends BlockVisitor implements Pass {
/// Returns a reference to an operand of [prim], where [prim] throws if null
/// is passed into that operand.
Reference<Primitive> getNullCheckedOperand(Primitive prim) {
if (prim is NullCheck) return prim.value;
if (prim is ReceiverCheck) return prim.value;
if (prim is GetLength) return prim.object;
if (prim is GetField) return prim.object;
if (prim is GetIndex) return prim.object;
@@ -71,7 +71,7 @@ class BackwardNullCheckRemover extends BlockVisitor implements Pass {
/// It has been determined that the null check in [prim] made redundant by
/// [newNullCheck]. Eliminate [prim] if it is not needed any more.
void tryEliminateRedundantNullCheck(Primitive prim, Primitive newNullCheck) {
if (prim is NullCheck) {
if (prim is ReceiverCheck && prim.isNullCheck) {
Primitive value = prim.value.definition;
LetPrim let = prim.parent;
prim..replaceUsesWith(value)..destroy();
+100 -38
View File
@@ -260,7 +260,7 @@ abstract class Primitive extends Variable<Primitive> {
// TODO(johnniwinther): Require source information for all primitives.
SourceInformation get sourceInformation => null;
/// If this is a [Refinement], [BoundsCheck] or [NullCheck] node, returns the
/// If this is a [Refinement], [BoundsCheck] or [ReceiverCheck] node, returns the
/// value being refined, the indexable object being checked, or the value
/// that was checked to be non-null, respectively.
///
@@ -805,7 +805,7 @@ class Refinement extends Primitive {
class BoundsCheck extends Primitive {
final Reference<Primitive> object;
Reference<Primitive> index;
Reference<Primitive> length; // FIXME write docs for length
Reference<Primitive> length;
int checks;
final SourceInformation sourceInformation;
@@ -879,53 +879,99 @@ class BoundsCheck extends Primitive {
Primitive get effectiveDefinition => object.definition.effectiveDefinition;
}
/// Throw an exception if [value] is `null`.
/// Throw a [NoSuchMethodError] if [value] cannot respond to [selector].
///
/// Returns [value] so this can be used to restrict code motion.
///
/// In the simplest form this compiles to `value.toString;`.
/// The check can take one of three forms:
///
/// [selector] holds the selector that is the cause of the null check. This is
/// usually a method that was inlined where [value] the receiver.
/// value.toString;
/// value.selectorName;
/// value.selectorName(); (should only be used if check always fails)
///
/// If [selector] is set and [useSelector] is true, `toString` is replaced with
/// the (possibly minified) invocation name of the selector. This can be
/// shorter and generate a more meaningful error message, but is expensive if
/// [value] is non-null and does not have that property at runtime.
/// The first two forms are used when it is known that only null fails the
/// check. Additionally, the check may be guarded by a [condition], allowing
/// for three more forms:
///
/// If [condition] is set, it is assumed that [condition] is true if and only
/// if [value] is null. The check then compiles to:
/// if (condition) value.toString; (this form is valid but unused)
/// if (condition) value.selectorName;
/// if (condition) value.selectorName();
///
/// if (condition) value.toString; (or .selector if non-null)
/// The condition must be true if and only if the check should fail. It should
/// ideally be of a form understood by JS engines, e.g. a `typeof` test.
///
/// The latter form is useful when [condition] is a form understood by the JS
/// runtime, such as a `typeof` test.
class NullCheck extends Primitive {
/// If [useSelector] is false, the first form instead becomes `value.toString;`.
/// This form is faster when the value is non-null and the accessed property has
/// been removed by tree shaking.
///
/// [selector] may not be one of the selectors implemented by the null object.
class ReceiverCheck extends Primitive {
final Reference<Primitive> value;
final Selector selector;
final bool useSelector;
final Reference<Primitive> condition;
final SourceInformation sourceInformation;
final Reference<Primitive> condition;
final int _flags;
NullCheck(Primitive value, this.sourceInformation,
{Primitive condition,
this.selector,
this.useSelector: false})
: this.value = new Reference<Primitive>(value),
this.condition =
condition == null ? null : new Reference<Primitive>(condition);
static const int _USE_SELECTOR = 1 << 0;
static const int _NULL_CHECK = 1 << 1;
NullCheck.guarded(Primitive condition, Primitive value, this.selector,
this.sourceInformation)
: this.condition = new Reference<Primitive>(condition),
this.value = new Reference<Primitive>(value),
this.useSelector = true;
/// True if the selector name should be used in the check; otherwise
/// `toString` will be used.
bool get useSelector => _flags & _USE_SELECTOR != 0;
/// True if null is the only possible input that cannot respond to [selector].
bool get isNullCheck => _flags & _NULL_CHECK != 0;
/// Constructor for creating checks in arbitrary configurations.
///
/// Consider using one of the named constructors instead.
///
/// [useSelector] and [isNullCheck] are mandatory named arguments.
ReceiverCheck(Primitive value, this.selector, this.sourceInformation,
{Primitive condition, bool useSelector, bool isNullCheck})
: value = new Reference<Primitive>(value),
condition = _optionalReference(condition),
_flags = (useSelector ? _USE_SELECTOR : 0) |
(isNullCheck ? _NULL_CHECK : 0);
/// Simplified constructor for building null checks.
///
/// Null must be the only possible input value that does not respond to
/// [selector].
ReceiverCheck.nullCheck(
Primitive value,
Selector selector,
SourceInformation sourceInformation,
{Primitive condition})
: this(value,
selector,
sourceInformation,
condition: condition,
useSelector: condition != null,
isNullCheck: true);
/// Simplified constructor for building the general check of form:
///
/// if (condition) value.selectorName();
///
ReceiverCheck.generalCheck(
Primitive value,
Selector selector,
SourceInformation sourceInformation,
Primitive condition)
: this(value,
selector,
sourceInformation,
condition: condition,
useSelector: true,
isNullCheck: false);
bool get isSafeForElimination => false;
bool get isSafeForReordering => false;
bool get hasValue => true;
accept(Visitor visitor) => visitor.visitNullCheck(this);
accept(Visitor visitor) => visitor.visitReceiverCheck(this);
void setParentPointers() {
value.parent = this;
@@ -935,6 +981,10 @@ class NullCheck extends Primitive {
}
Primitive get effectiveDefinition => value.definition.effectiveDefinition;
String get nullCheckString => isNullCheck ? 'null-check' : 'general-check';
String get useSelectorString => useSelector ? 'use-selector' : 'no-selector';
String get flagString => '$nullCheckString $useSelectorString';
}
/// An "is" type test.
@@ -1930,6 +1980,16 @@ class Yield extends UnsafePrimitive {
}
}
Reference<Primitive> _reference(Primitive definition) {
return new Reference<Primitive>(definition);
}
Reference<Primitive> _optionalReference(Primitive definition) {
return definition == null
? null
: new Reference<Primitive>(definition);
}
List<Reference<Primitive>> _referenceList(Iterable<Primitive> definitions) {
return definitions.map((e) => new Reference<Primitive>(e)).toList();
}
@@ -2079,7 +2139,7 @@ abstract class Visitor<T> implements BlockVisitor<T> {
T visitSetIndex(SetIndex node);
T visitRefinement(Refinement node);
T visitBoundsCheck(BoundsCheck node);
T visitNullCheck(NullCheck node);
T visitReceiverCheck(ReceiverCheck node);
T visitForeignCode(ForeignCode node);
}
@@ -2402,8 +2462,8 @@ class DeepRecursiveVisitor implements Visitor {
}
}
processNullCheck(NullCheck node) {}
visitNullCheck(NullCheck node) {
processNullCheck(ReceiverCheck node) {}
visitReceiverCheck(ReceiverCheck node) {
processNullCheck(node);
processReference(node.value);
if (node.condition != null) {
@@ -2763,11 +2823,13 @@ class DefinitionCopyingVisitor extends Visitor<Definition> {
}
}
Definition visitNullCheck(NullCheck node) {
return new NullCheck(getCopy(node.value), node.sourceInformation,
Definition visitReceiverCheck(ReceiverCheck node) {
return new ReceiverCheck(getCopy(node.value),
node.selector,
node.sourceInformation,
condition: node.condition == null ? null : getCopy(node.condition),
selector: node.selector,
useSelector: node.useSelector);
useSelector: node.useSelector,
isNullCheck: node.isNullCheck);
}
Definition visitForeignCode(ForeignCode node) {
@@ -448,10 +448,11 @@ class SExpressionStringifier extends Indentation implements Visitor<String> {
return '(BoundsCheck $object $index $length ${node.checkString})';
}
String visitNullCheck(NullCheck node) {
String visitReceiverCheck(ReceiverCheck node) {
String value = access(node.value);
String condition = optionalAccess(node.condition);
return '(NullCheck $value $condition (${node.selector ?? ""}))';
return '(ReceiverCheck $value ${node.selector} $condition '
'${node.flagString}))';
}
}
@@ -393,10 +393,11 @@ class IRTracer extends TracerUtil implements cps_ir.Visitor {
return 'BoundsCheck $object $index $length ${node.checkString}';
}
visitNullCheck(cps_ir.NullCheck node) {
visitReceiverCheck(cps_ir.ReceiverCheck node) {
String value = formatReference(node.value);
String condition = formatReference(node.condition);
return 'NullCheck $value condition:$condition selector:${node.selector}';
return 'ReceiverCheck $value $condition ${node.selector} '
'${node.flagString}';
}
}
@@ -681,7 +682,7 @@ class BlockCollector implements cps_ir.Visitor {
unexpectedNode(node);
}
visitNullCheck(cps_ir.NullCheck node) {
visitReceiverCheck(cps_ir.ReceiverCheck node) {
unexpectedNode(node);
}
}
+1 -1
View File
@@ -192,7 +192,7 @@ class GVN extends TrampolineRecursiveVisitor implements Pass {
bool isHoistablePrimitive(Primitive prim) {
if (prim.isSafeForElimination) return true;
if (prim is NullCheck ||
if (prim is ReceiverCheck ||
prim is BoundsCheck ||
prim is GetLength ||
prim is GetField ||
+28 -5
View File
@@ -405,6 +405,14 @@ class InliningVisitor extends TrampolineRecursiveVisitor {
return null;
}
if (isBlacklisted(target)) return null;
if (invoke.callingConvention == CallingConvention.OneShotIntercepted) {
// One-shot interceptor calls with a known target are only inserted on
// uncommon code paths, so they should not be inlined.
return null;
}
Reference<Primitive> dartReceiver = invoke.dartReceiverReference;
TypeMask abstractReceiver =
dartReceiver == null ? null : abstractType(dartReceiver);
@@ -512,7 +520,9 @@ class InliningVisitor extends TrampolineRecursiveVisitor {
CpsFragment fragment,
Primitive dartReceiver,
TypeMask abstractReceiver) {
Selector selector = invoke is InvokeMethod ? invoke.selector : null;
if (invoke is! InvokeMethod) return dartReceiver;
InvokeMethod invokeMethod = invoke;
Selector selector = invokeMethod.selector;
if (typeSystem.isDefinitelyNum(abstractReceiver, allowNull: true)) {
Primitive condition = _fragment.letPrim(
new ApplyBuiltinOperator(BuiltinOperator.IsNotNumber,
@@ -520,15 +530,16 @@ class InliningVisitor extends TrampolineRecursiveVisitor {
invoke.sourceInformation));
condition.type = typeSystem.boolType;
Primitive check = _fragment.letPrim(
new NullCheck.guarded(
condition, dartReceiver, selector, invoke.sourceInformation));
new ReceiverCheck.nullCheck(dartReceiver, selector,
invoke.sourceInformation,
condition: condition));
check.type = abstractReceiver.nonNullable();
return check;
}
Primitive check = _fragment.letPrim(
new NullCheck(dartReceiver, invoke.sourceInformation,
selector: selector));
new ReceiverCheck.nullCheck(dartReceiver, selector,
invoke.sourceInformation));
check.type = abstractReceiver.nonNullable();
return check;
}
@@ -571,4 +582,16 @@ class InliningVisitor extends TrampolineRecursiveVisitor {
}
return tryInlining(node, node.target, null);
}
bool isBlacklisted(FunctionElement target) {
ClassElement enclosingClass = target.enclosingClass;
if (target.isOperator &&
(enclosingClass == backend.helpers.jsNumberClass ||
enclosingClass == backend.helpers.jsDoubleClass ||
enclosingClass == backend.helpers.jsIntClass)) {
// These should be handled by operator specialization.
return true;
}
return false;
}
}
@@ -212,7 +212,7 @@ class RedundantJoinEliminator extends TrampolineRecursiveVisitor implements Pass
/// After lifting LetConts in the main pass above, parameter objects can have
/// multiple bindings. Each reference implicitly refers to the binding that
/// is currently in scope.
///
///
/// This returns the IR to its normal form after redundant joins have been
/// eliminated.
class AlphaRenamer extends TrampolineRecursiveVisitor {
+234 -63
View File
@@ -702,6 +702,17 @@ class ConstantPropagationLattice {
return nonConstant(value.type.nonNullable());
}
AbstractConstantValue intersectWithType(AbstractConstantValue value,
TypeMask type) {
if (value.isNothing || typeSystem.areDisjoint(value.type, type)) {
return nothing;
} else if (value.isConstant) {
return value;
} else {
return nonConstant(typeSystem.intersection(value.type, type));
}
}
/// If [value] is an integer constant, returns its value, otherwise `null`.
int intValue(AbstractConstantValue value) {
if (value.isConstant && value.constant.isInt) {
@@ -798,11 +809,17 @@ class TransformingVisitor extends DeepRecursiveVisitor {
final List<Node> stack = <Node>[];
TypeCheckOperator checkIsNumber;
TransformingVisitor(this.compiler,
this.functionCompiler,
this.lattice,
this.analyzer,
this.internalError);
this.internalError) {
checkIsNumber = new ClassTypeCheckOperator(
helpers.jsNumberClass,
BuiltinOperator.IsNotNumber);
}
void transform(FunctionDefinition root) {
// If one of the parameters has no value, the function is unreachable.
@@ -1116,41 +1133,99 @@ class TransformingVisitor extends DeepRecursiveVisitor {
///
/// Returns `true` if the node was replaced.
specializeOperatorCall(InvokeMethod node) {
if (!backend.isInterceptedSelector(node.selector)) return null;
if (node.dartArgumentsLength > 1) return null;
if (node.callingConvention == CallingConvention.OneShotIntercepted) {
return null;
}
bool trustPrimitives = compiler.trustPrimitives;
/// Throws a [NoSuchMethodError] if the receiver is null, where [guard]
/// is a predicate that is true if and only if the receiver is null.
///
/// See [NullCheck.guarded].
Primitive guardReceiver(CpsFragment cps, BuiltinOperator guard) {
if (guard == null || getValue(node.dartReceiver).isDefinitelyNotNull) {
return node.dartReceiver;
/// Check that the receiver and argument satisfy the given type checks, and
/// throw a [NoSuchMethodError] or [ArgumentError] if the check fails.
CpsFragment makeGuard(TypeCheckOperator receiverGuard,
[TypeCheckOperator argumentGuard]) {
CpsFragment cps = new CpsFragment(node.sourceInformation);
// Make no guards if trusting primitives.
if (trustPrimitives) return cps;
// Determine which guards are needed.
ChecksNeeded receiverChecks =
receiverGuard.getChecksNeeded(node.dartReceiver, classWorld);
bool needReceiverGuard = receiverChecks != ChecksNeeded.None;
bool needArgumentGuard =
argumentGuard != null &&
argumentGuard.needsCheck(node.dartArgument(0), classWorld);
if (!needReceiverGuard && !needArgumentGuard) return cps;
// If we only need the receiver check, emit the specialized receiver
// check instruction. Examples:
//
// if (typeof receiver !== "number") return receiver.$lt;
// if (typeof receiver !== "number") return receiver.$lt();
//
if (!needArgumentGuard) {
Primitive condition = receiverGuard.makeCheck(cps, node.dartReceiver);
cps.letPrim(new ReceiverCheck(
node.dartReceiver,
node.selector,
node.sourceInformation,
condition: condition,
useSelector: true,
isNullCheck: receiverChecks == ChecksNeeded.Null
));
return cps;
}
if (!trustPrimitives) {
// TODO(asgerf): Perhaps a separate optimization should decide that
// the guarded check is better based on the type?
Primitive check = cps.applyBuiltin(guard, [node.dartReceiver]);
return cps.letPrim(new NullCheck.guarded(check, node.dartReceiver,
node.selector, node.sourceInformation));
} else {
// Refine the receiver to be non-null for use in the operator.
// This restricts code motion and improves the type computed for the
// built-in operator that depends on it.
// This must be done even if trusting primitives.
return cps.letPrim(
new Refinement(node.dartReceiver, typeSystem.nonNullType));
// TODO(asgerf): We should consider specialized instructions for
// argument checks and receiver+argument checks, to avoid breaking up
// basic blocks.
// Emit as `H.iae(x)` if only the argument check may fail. For example:
//
// if (typeof argument !== "number") return H.iae(argument);
//
if (!needReceiverGuard) {
cps.ifTruthy(argumentGuard.makeCheck(cps, node.dartArgument(0)))
.invokeStaticThrower(helpers.throwIllegalArgumentException,
[node.dartArgument(0)]);
return cps;
}
// Both receiver and argument check is needed. Emit as a combined check
// using a one-shot interceptor to produce the exact error message in
// the error case. For example:
//
// if (typeof receiver !== "number" || typeof argument !== "number")
// return J.$lt(receiver, argument);
//
Continuation fail = cps.letCont();
cps.ifTruthy(receiverGuard.makeCheck(cps, node.dartReceiver))
.invokeContinuation(fail);
cps.ifTruthy(argumentGuard.makeCheck(cps, node.dartArgument(0)))
.invokeContinuation(fail);
cps.insideContinuation(fail)
..invokeMethod(node.dartReceiver, node.selector, node.mask,
[node.dartArgument(0)], CallingConvention.OneShotIntercepted)
..put(new Unreachable());
return cps;
}
/// Replaces the call with [operator], using the receiver and first argument
/// as operands (in that order).
///
/// If [guard] is given, the receiver is checked using [guardReceiver],
/// unless it is known not to be null.
CpsFragment makeBinary(BuiltinOperator operator, {BuiltinOperator guard}) {
CpsFragment cps = new CpsFragment(node.sourceInformation);
Primitive left = guardReceiver(cps, guard);
Primitive right = node.dartArgument(0);
/// If [guard] is given, the receiver and argument are both checked using
/// that operator.
CpsFragment makeBinary(BuiltinOperator operator,
{TypeCheckOperator guard: TypeCheckOperator.none}) {
CpsFragment cps = makeGuard(guard, guard);
Primitive left = guard.makeRefinement(cps, node.dartReceiver, classWorld);
Primitive right =
guard.makeRefinement(cps, node.dartArgument(0), classWorld);
Primitive result = cps.applyBuiltin(operator, [left, right]);
result.hint = node.hint;
node.replaceUsesWith(result);
@@ -1159,15 +1234,20 @@ class TransformingVisitor extends DeepRecursiveVisitor {
/// Like [makeBinary] but for unary operators with the receiver as the
/// argument.
CpsFragment makeUnary(BuiltinOperator operator, {BuiltinOperator guard}) {
CpsFragment cps = new CpsFragment(node.sourceInformation);
Primitive argument = guardReceiver(cps, guard);
CpsFragment makeUnary(BuiltinOperator operator,
{TypeCheckOperator guard: TypeCheckOperator.none}) {
CpsFragment cps = makeGuard(guard);
Primitive argument =
guard.makeRefinement(cps, node.dartReceiver, classWorld);
Primitive result = cps.applyBuiltin(operator, [argument]);
result.hint = node.hint;
node.replaceUsesWith(result);
return cps;
}
TypeMask successType =
typeSystem.receiverTypeFor(node.selector, node.dartReceiver.type);
if (node.selector.isOperator && node.dartArgumentsLength == 1) {
Primitive leftArg = node.dartReceiver;
Primitive rightArg = node.dartArgument(0);
@@ -1198,13 +1278,17 @@ class TransformingVisitor extends DeepRecursiveVisitor {
return makeBinary(BuiltinOperator.Identical);
}
} else {
if (lattice.isDefinitelyNum(left, allowNull: true) &&
lattice.isDefinitelyNum(right, allowNull: trustPrimitives)) {
if (typeSystem.isDefinitelyNum(successType)) {
// Try to insert a numeric operator.
BuiltinOperator operator = NumBinaryBuiltins[opname];
if (operator != null) {
return makeBinary(operator, guard: BuiltinOperator.IsNotNumber);
return makeBinary(operator, guard: checkIsNumber);
}
// The following specializations only apply to integers.
// The Math.floor test is quite large, so we only apply these in cases
// where the guard does not involve Math.floor.
// Shift operators are not in [NumBinaryBuiltins] because Dart shifts
// behave different to JS shifts, especially in the handling of the
// shift count.
@@ -1212,8 +1296,7 @@ class TransformingVisitor extends DeepRecursiveVisitor {
if (opname == '<<' &&
lattice.isDefinitelyInt(left, allowNull: true) &&
lattice.isDefinitelyIntInRange(right, min: 0, max: 31)) {
return makeBinary(BuiltinOperator.NumShl,
guard: BuiltinOperator.IsNotNumber);
return makeBinary(BuiltinOperator.NumShl, guard: checkIsNumber);
}
// Try to insert a shift-right operator. JavaScript's right shift is
// consistent with Dart's only for left operands in the unsigned
@@ -1221,8 +1304,7 @@ class TransformingVisitor extends DeepRecursiveVisitor {
if (opname == '>>' &&
lattice.isDefinitelyUint32(left, allowNull: true) &&
lattice.isDefinitelyIntInRange(right, min: 0, max: 31)) {
return makeBinary(BuiltinOperator.NumShr,
guard: BuiltinOperator.IsNotNumber);
return makeBinary(BuiltinOperator.NumShr, guard: checkIsNumber);
}
// Try to use remainder for '%'. Both operands must be non-negative
// and the divisor must be non-zero.
@@ -1231,14 +1313,14 @@ class TransformingVisitor extends DeepRecursiveVisitor {
lattice.isDefinitelyUint(right) &&
lattice.isDefinitelyIntInRange(right, min: 1)) {
return makeBinary(BuiltinOperator.NumRemainder,
guard: BuiltinOperator.IsNotNumber);
guard: checkIsNumber);
}
if (opname == '~/' &&
lattice.isDefinitelyUint32(left, allowNull: true) &&
lattice.isDefinitelyIntInRange(right, min: 2)) {
return makeBinary(BuiltinOperator.NumTruncatingDivideToSigned32,
guard: BuiltinOperator.IsNotNumber);
guard: checkIsNumber);
}
}
if (lattice.isDefinitelyString(left, allowNull: trustPrimitives) &&
@@ -1250,18 +1332,13 @@ class TransformingVisitor extends DeepRecursiveVisitor {
}
}
if (node.selector.isOperator && node.dartArgumentsLength == 0) {
Primitive argument = node.dartReceiver;
AbstractConstantValue value = getValue(argument);
if (lattice.isDefinitelyNum(value, allowNull: true)) {
if (typeSystem.isDefinitelyNum(successType)) {
String opname = node.selector.name;
if (opname == '~') {
return makeUnary(BuiltinOperator.NumBitNot,
guard: BuiltinOperator.IsNotNumber);
return makeUnary(BuiltinOperator.NumBitNot, guard: checkIsNumber);
}
if (opname == 'unary-') {
return makeUnary(BuiltinOperator.NumNegate,
guard: BuiltinOperator.IsNotNumber);
return makeUnary(BuiltinOperator.NumNegate, guard: checkIsNumber);
}
}
}
@@ -1277,7 +1354,7 @@ class TransformingVisitor extends DeepRecursiveVisitor {
lattice.isDefinitelyInt(argValue) &&
isIntNotZero(argValue)) {
return makeBinary(BuiltinOperator.NumRemainder,
guard: BuiltinOperator.IsNotNumber);
guard: checkIsNumber);
}
}
} else if (name == 'codeUnitAt') {
@@ -1391,6 +1468,7 @@ class TransformingVisitor extends DeepRecursiveVisitor {
case '[]':
Primitive index = node.dartArgument(0);
// TODO(asgerf): Consider inserting a guard and specialize anyway.
if (!lattice.isDefinitelyInt(getValue(index))) return null;
CpsFragment cps = new CpsFragment(node.sourceInformation);
receiver = makeBoundsCheck(cps, receiver, index);
@@ -2282,9 +2360,12 @@ class TransformingVisitor extends DeepRecursiveVisitor {
}
}
visitNullCheck(NullCheck node) {
if (!getValue(node.value.definition).isNullable) {
node.replaceUsesWith(node.value.definition);
visitReceiverCheck(ReceiverCheck node) {
Primitive input = node.value.definition;
if (!input.type.isNullable &&
(node.isNullCheck ||
!input.type.needsNoSuchMethodHandling(node.selector, classWorld))) {
node.replaceUsesWith(input);
return new CpsFragment();
}
return null;
@@ -3128,16 +3209,9 @@ class TypePropagationVisitor implements Visitor {
@override
void visitRefinement(Refinement node) {
AbstractConstantValue value = getValue(node.value.definition);
if (value.isNothing ||
typeSystem.areDisjoint(value.type, node.refineType)) {
setValue(node, nothing);
} else if (value.isConstant) {
setValue(node, value);
} else {
setValue(node,
nonConstant(value.type.intersection(node.refineType, classWorld)));
}
setValue(node, lattice.intersectWithType(
getValue(node.value.definition),
node.refineType));
}
@override
@@ -3146,8 +3220,19 @@ class TypePropagationVisitor implements Visitor {
}
@override
void visitNullCheck(NullCheck node) {
setValue(node, lattice.nonNullable(getValue(node.value.definition)));
void visitReceiverCheck(ReceiverCheck node) {
AbstractConstantValue value = getValue(node.value.definition);
if (node.isNullCheck) {
// Avoid expensive TypeMask operations for null checks.
setValue(node, lattice.nonNullable(value));
} else if (value.isConstant &&
!value.type.needsNoSuchMethodHandling(node.selector, classWorld)) {
// Preserve constants, unless the check fails for the constant.
setValue(node, value);
} else {
setValue(node,
nonConstant(typeSystem.receiverTypeFor(node.selector, value.type)));
}
}
}
@@ -3269,3 +3354,89 @@ class ResetAnalysisInfo extends TrampolineRecursiveVisitor {
clear(node.variable);
}
}
enum ChecksNeeded {
/// No check is needed.
None,
/// Only null may fail the check.
Null,
/// Full check required.
Complete,
}
/// Generates runtime checks against a some type criteria, and determines at
/// compile-time if the check is needed.
///
/// This class only generates the condition for determining if a check should
/// fail. Throwing the appropriate error in response to a failure is handled
/// elsewhere.
abstract class TypeCheckOperator {
const TypeCheckOperator();
static const TypeCheckOperator none = const NoTypeCheckOperator();
/// Determines to what extent a runtime check is needed.
///
/// Sometimes a check can be slightly improved if it is known that null is the
/// only possible input that fails the check.
ChecksNeeded getChecksNeeded(Primitive value, World world);
/// Make an expression that returns `true` if [value] should fail the check.
///
/// The result should be used in a check of the form:
///
/// if (makeCheck(value)) throw Error(value);
///
Primitive makeCheck(CpsFragment cps, Primitive value);
/// Refine [value] after a succesful check.
Primitive makeRefinement(CpsFragment cps, Primitive value, World world);
bool needsCheck(Primitive value, World world) {
return getChecksNeeded(value, world) != ChecksNeeded.None;
}
}
/// Check that always passes.
class NoTypeCheckOperator extends TypeCheckOperator {
const NoTypeCheckOperator();
ChecksNeeded getChecksNeeded(Primitive value, World world) {
return ChecksNeeded.None;
}
Primitive makeCheck(CpsFragment cps, Primitive value) {
return cps.makeFalse();
}
Primitive makeRefinement(CpsFragment cps, Primitive value, World world) {
return value;
}
}
/// Checks using a built-in operator that a value is an instance of a given
/// class.
class ClassTypeCheckOperator extends TypeCheckOperator {
ClassElement classElement;
BuiltinOperator negatedOperator;
ClassTypeCheckOperator(this.classElement, this.negatedOperator);
ChecksNeeded getChecksNeeded(Primitive value, World world) {
TypeMask type = value.type;
if (type.satisfies(classElement, world)) {
return type.isNullable ? ChecksNeeded.Null : ChecksNeeded.None;
} else {
return ChecksNeeded.Complete;
}
}
Primitive makeCheck(CpsFragment cps, Primitive value) {
return cps.applyBuiltin(negatedOperator, [value]);
}
Primitive makeRefinement(CpsFragment cps, Primitive value, World world) {
return cps.refine(value, new TypeMask.nonNullSubclass(classElement, world));
}
}
@@ -3,6 +3,7 @@ library dart2js.cps_ir.update_refinements;
import 'cps_ir_nodes.dart';
import 'optimizers.dart' show Pass;
import 'type_mask_system.dart';
import '../world.dart';
/// Updates all references to use the most refined version in scope.
///
@@ -19,6 +20,7 @@ class UpdateRefinements extends TrampolineRecursiveVisitor implements Pass {
String get passName => 'Update refinements';
final TypeMaskSystem typeSystem;
World get classWorld => typeSystem.classWorld;
Map<Primitive, Primitive> refinementFor = <Primitive, Primitive>{};
@@ -34,20 +36,21 @@ class UpdateRefinements extends TrampolineRecursiveVisitor implements Pass {
return next;
}
visitNullCheck(NullCheck node) {
visitReceiverCheck(ReceiverCheck node) {
if (refine(node.value)) {
// Update the type if the input has changed.
Primitive value = node.value.definition;
if (value.type.isNullable) {
// Update the type if the input has changed.
node.type = value.type.nonNullable();
if (value.type.needsNoSuchMethodHandling(node.selector, classWorld)) {
node.type = typeSystem.receiverTypeFor(node.selector, value.type);
} else {
// Check is no longer needed.
node..replaceUsesWith(value)..destroy();
LetPrim letPrim = node.parent;
letPrim.remove();
return;
}
}
// Use the NullCheck as a refinement.
// Use the ReceiverCheck as a refinement.
Primitive value = node.effectiveDefinition;
Primitive old = refinementFor[value];
refinementFor[value] = node;
@@ -1031,7 +1031,7 @@ class CodeGenerator extends tree_ir.StatementVisitor
}
@override
visitNullCheck(tree_ir.NullCheck node) {
visitReceiverCheck(tree_ir.ReceiverCheck node) {
js.Expression value = visitExpression(node.value);
// TODO(sra): Try to use the selector even when [useSelector] is false. The
// reason we use 'toString' is that it is always defined so avoids a slow
@@ -1041,9 +1041,12 @@ class CodeGenerator extends tree_ir.StatementVisitor
// hook for that selector. We don't know these things here, but the decision
// could be deferred by creating a deferred property that was resolved after
// codegen.
js.Expression access = node.selector != null && node.useSelector
js.Expression access = node.useSelector
? js.js('#.#', [value, glue.invocationName(node.selector)])
: js.js('#.toString', [value]);
if (node.useInvoke) {
access = new js.Call(access, []);
}
if (node.condition != null) {
js.Expression condition = visitExpression(node.condition);
js.Statement body = isNullReturn(node.next)
@@ -168,7 +168,7 @@ class PullIntoInitializers extends RecursiveTransformer
return node;
}
Statement visitNullCheck(NullCheck node) {
Statement visitReceiverCheck(ReceiverCheck node) {
if (node.condition != null) {
node.condition = visitExpression(node.condition);
// The value occurs in conditional context, so don't pull from that.
@@ -1261,7 +1261,7 @@ class StatementRewriter extends Transformer implements Pass {
}
@override
Statement visitNullCheck(NullCheck node) {
Statement visitReceiverCheck(ReceiverCheck node) {
inEmptyEnvironment(() {
node.next = visitStatement(node.next);
});
@@ -699,12 +699,16 @@ class Builder implements cps_ir.Visitor/*<NodeCallback|Node>*/ {
}
}
visitNullCheck(cps_ir.NullCheck node) => (Statement next) {
return new NullCheck(
visitReceiverCheck(cps_ir.ReceiverCheck node) => (Statement next) {
// The CPS IR uses 'isNullCheck' because the semantics are important.
// In the Tree IR, syntax is more important, so the receiver check uses
// "useInvoke" to denote if an invocation should be emitted.
return new ReceiverCheck(
condition: getVariableUseOrNull(node.condition),
value: getVariableUse(node.value),
selector: node.selector,
useSelector: node.useSelector,
useInvoke: !node.isNullCheck,
next: next,
sourceInformation: node.sourceInformation);
};
@@ -950,23 +950,24 @@ class Yield extends Statement {
}
}
class NullCheck extends Statement {
class ReceiverCheck extends Statement {
Expression condition;
Expression value;
Selector selector;
bool useSelector;
bool useInvoke;
Statement next;
SourceInformation sourceInformation;
NullCheck({this.condition, this.value, this.selector, this.useSelector,
this.next, this.sourceInformation});
ReceiverCheck({this.condition, this.value, this.selector, this.useSelector,
this.useInvoke, this.next, this.sourceInformation});
accept(StatementVisitor visitor) {
return visitor.visitNullCheck(this);
return visitor.visitReceiverCheck(this);
}
accept1(StatementVisitor1 visitor, arg) {
return visitor.visitNullCheck(this, arg);
return visitor.visitReceiverCheck(this, arg);
}
}
@@ -1059,7 +1060,7 @@ abstract class StatementVisitor<S> {
S visitUnreachable(Unreachable node);
S visitForeignStatement(ForeignStatement node);
S visitYield(Yield node);
S visitNullCheck(NullCheck node);
S visitReceiverCheck(ReceiverCheck node);
}
abstract class StatementVisitor1<S, A> {
@@ -1077,7 +1078,7 @@ abstract class StatementVisitor1<S, A> {
S visitUnreachable(Unreachable node, A arg);
S visitForeignStatement(ForeignStatement node, A arg);
S visitYield(Yield node, A arg);
S visitNullCheck(NullCheck node, A arg);
S visitReceiverCheck(ReceiverCheck node, A arg);
}
abstract class RecursiveVisitor implements StatementVisitor, ExpressionVisitor {
@@ -1286,7 +1287,7 @@ abstract class RecursiveVisitor implements StatementVisitor, ExpressionVisitor {
visitStatement(node.next);
}
visitNullCheck(NullCheck node) {
visitReceiverCheck(ReceiverCheck node) {
if (node.condition != null) visitExpression(node.condition);
visitExpression(node.value);
visitStatement(node.next);
@@ -1546,7 +1547,7 @@ class RecursiveTransformer extends Transformer {
return node;
}
visitNullCheck(NullCheck node) {
visitReceiverCheck(ReceiverCheck node) {
if (node.condition != null) {
node.condition = visitExpression(node.condition);
}
@@ -179,7 +179,7 @@ class BlockCollector extends StatementVisitor {
visitStatement(node.next);
}
visitNullCheck(NullCheck node) {
visitReceiverCheck(ReceiverCheck node) {
_addStatement(node);
visitStatement(node.next);
}
@@ -345,7 +345,7 @@ class TreeTracer extends TracerUtil with StatementVisitor {
}
@override
visitNullCheck(NullCheck node) {
visitReceiverCheck(ReceiverCheck node) {
printStatement(null, 'NullCheck ${expr(node.value)}');
}
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$sub$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$sub$n(x, y);
P.print(x - y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$gt$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$gt$n(x, y);
P.print(x > y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$lt$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$lt$n(x, y);
P.print(x < y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$ge$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$ge$n(x, y);
P.print(x >= y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$le$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$le$n(x, y);
P.print(x <= y);
P.print(true);
P.print(true);
}
@@ -10,12 +10,14 @@
// }
function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null), v0 = typeof y === "number";
P.print(J.$div$n(x, 2));
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
if (typeof x !== "number")
return x.$div();
P.print(x / 2);
P.print(true);
P.print(v0);
if (!v0)
throw H.wrapException(H.argumentErrorValue(y));
P.print(typeof y === "number");
if (typeof y !== "number")
return H.iae(y);
P.print(x + y);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$div$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$div$n(x, y);
P.print(x / y);
P.print(true);
P.print(true);
}
@@ -10,12 +10,14 @@
// }
function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null), v0 = typeof y === "number";
P.print(J.$div$n(x, 2));
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
if (typeof x !== "number")
return x.$div();
P.print(x / 2);
P.print(true);
P.print(v0);
if (!v0)
throw H.wrapException(H.argumentErrorValue(y));
P.print(typeof y === "number");
if (typeof y !== "number")
return H.iae(y);
P.print(x * y);
P.print(true);
}
@@ -10,9 +10,12 @@
// }
function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null), v0 = typeof y === "number";
P.print(J.$div$n(x, 2));
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null), v0;
if (typeof x !== "number")
return x.$div();
P.print(x / 2);
P.print(true);
v0 = typeof y === "number";
P.print(v0);
if (!v0)
throw H.wrapException(H.argumentErrorValue(y));
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$and$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$and$n(x, y);
P.print((x & y) >>> 0);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$or$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$or$n(x, y);
P.print((x | y) >>> 0);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$xor$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$xor$n(x, y);
P.print((x ^ y) >>> 0);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$sub$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$sub$n(x, y);
P.print(x - y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$lt$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$lt$n(x, y);
P.print(x < y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$gt$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$gt$n(x, y);
P.print(x > y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$le$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$le$n(x, y);
P.print(x <= y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$ge$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$ge$n(x, y);
P.print(x >= y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$div$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$div$n(x, y);
P.print(x / y);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$and$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$and$n(x, y);
P.print((x & y) >>> 0);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$or$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$or$n(x, y);
P.print((x | y) >>> 0);
P.print(true);
P.print(true);
}
@@ -13,7 +13,9 @@ function() {
var x = P.int_parse("1233", null, null), y = P.int_parse("1234", null, null);
P.print(typeof x === "number");
P.print(typeof y === "number");
P.print(J.$xor$n(x, y));
if (typeof x !== "number" || typeof y !== "number")
return J.$xor$n(x, y);
P.print((x ^ y) >>> 0);
P.print(true);
P.print(true);
}
@@ -1,12 +1,14 @@
// Expectation for test:
// // Method to test: function(foo)
// foo(a) => a % 13;
// import 'package:expect/expect.dart';
//
// @NoInline() foo(a) => a % 13;
//
// main() {
// print(foo(5));
// print(foo(-100));
// }
function(a) {
var result = a % 13;
return result === 0 ? 0 : result > 0 ? result : result + 13;
return C.JSInt_methods.$mod(a, 13);
}
@@ -1,11 +1,14 @@
// Expectation for test:
// // Method to test: function(foo)
// foo(a) => a ~/ 13;
// import 'package:expect/expect.dart';
//
// @NoInline() foo(a) => a ~/ 13;
//
// main() {
// print(foo(5));
// print(foo(-100));
// }
function(a) {
return (a | 0) === a && (13 | 0) === 13 ? a / 13 | 0 : C.JSNumber_methods.toInt$0(a / 13);
return C.JSInt_methods.$tdiv(a, 13);
}
@@ -1,6 +1,9 @@
// Expectation for test:
// // Method to test: function(foo)
// foo(a) => a ~/ 13;
// import 'package:expect/expect.dart';
//
// @NoInline() foo(a) => a ~/ 13;
//
// main() {
// print(foo.toString());
// print(foo(5));
@@ -1,11 +1,14 @@
// Expectation for test:
// // Method to test: function(foo)
// foo(a) => a ~/ 13;
// import 'package:expect/expect.dart';
//
// @NoInline() foo(a) => a ~/ 13;
//
// main() {
// print(foo(5));
// print(foo(8000000000));
// }
function(a) {
return (a | 0) === a && (13 | 0) === 13 ? a / 13 | 0 : C.JSNumber_methods.toInt$0(a / 13);
return C.JSInt_methods.$tdiv(a, 13);
}
@@ -1,5 +1,8 @@
// Method to test: function(foo)
foo(a) => a % 13;
import 'package:expect/expect.dart';
@NoInline() foo(a) => a % 13;
main() {
print(foo(5));
print(foo(-100));
@@ -1,5 +1,8 @@
// Method to test: function(foo)
foo(a) => a ~/ 13;
import 'package:expect/expect.dart';
@NoInline() foo(a) => a ~/ 13;
main() {
print(foo(5));
print(foo(-100));
@@ -1,5 +1,8 @@
// Method to test: function(foo)
foo(a) => a ~/ 13;
import 'package:expect/expect.dart';
@NoInline() foo(a) => a ~/ 13;
main() {
print(foo.toString());
print(foo(5));
@@ -1,5 +1,8 @@
// Method to test: function(foo)
foo(a) => a ~/ 13;
import 'package:expect/expect.dart';
@NoInline() foo(a) => a ~/ 13;
main() {
print(foo(5));
print(foo(8000000000));