blob: 5ea9e2935f05d3906915e42fdd1546b6578a0b14 [file] [log] [blame]
// Copyright (c) 2013, 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.
part of ssa;
/**
* [InvokeDynamicSpecializer] and its subclasses are helpers to
* optimize intercepted dynamic calls. It knows what input types
* would be beneficial for performance, and how to change a invoke
* dynamic to a builtin instruction (e.g. HIndex, HBitNot).
*/
class InvokeDynamicSpecializer {
const InvokeDynamicSpecializer();
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
return HType.UNKNOWN;
}
HType computeTypeFromInputTypes(HInvokeDynamic instruction,
Compiler compiler) {
HType receiverType = instruction.getDartReceiver(compiler).instructionType;
Selector refined = receiverType.refine(instruction.selector, compiler);
HType type = new HType.inferredTypeForSelector(refined, compiler);
// TODO(ngeoffray): Because we don't know yet the side effects of
// a JS call, we sometimes know more in the compiler about the
// side effects of an element (for example operator% on the int
// class). We should remove this check once we analyze JS calls.
if (!instruction.useGvn()) {
instruction.sideEffects =
compiler.world.getSideEffectsOfSelector(refined);
}
return type;
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
return null;
}
Operation operation(ConstantSystem constantSystem) => null;
static InvokeDynamicSpecializer lookupSpecializer(Selector selector) {
if (selector.kind == SelectorKind.INDEX) {
return selector.name == const SourceString('[]')
? const IndexSpecializer()
: const IndexAssignSpecializer();
} else if (selector.kind == SelectorKind.OPERATOR) {
if (selector.name == const SourceString('unary-')) {
return const UnaryNegateSpecializer();
} else if (selector.name == const SourceString('~')) {
return const BitNotSpecializer();
} else if (selector.name == const SourceString('+')) {
return const AddSpecializer();
} else if (selector.name == const SourceString('-')) {
return const SubtractSpecializer();
} else if (selector.name == const SourceString('*')) {
return const MultiplySpecializer();
} else if (selector.name == const SourceString('/')) {
return const DivideSpecializer();
} else if (selector.name == const SourceString('~/')) {
return const TruncatingDivideSpecializer();
} else if (selector.name == const SourceString('%')) {
return const ModuloSpecializer();
} else if (selector.name == const SourceString('>>')) {
return const ShiftRightSpecializer();
} else if (selector.name == const SourceString('<<')) {
return const ShiftLeftSpecializer();
} else if (selector.name == const SourceString('&')) {
return const BitAndSpecializer();
} else if (selector.name == const SourceString('|')) {
return const BitOrSpecializer();
} else if (selector.name == const SourceString('^')) {
return const BitXorSpecializer();
} else if (selector.name == const SourceString('==')) {
return const EqualsSpecializer();
} else if (selector.name == const SourceString('<')) {
return const LessSpecializer();
} else if (selector.name == const SourceString('<=')) {
return const LessEqualSpecializer();
} else if (selector.name == const SourceString('>')) {
return const GreaterSpecializer();
} else if (selector.name == const SourceString('>=')) {
return const GreaterEqualSpecializer();
}
}
return const InvokeDynamicSpecializer();
}
}
class IndexAssignSpecializer extends InvokeDynamicSpecializer {
const IndexAssignSpecializer();
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
HInstruction index = instruction.inputs[2];
if (input == instruction.inputs[1] &&
index.instructionType.canBePrimitiveNumber(compiler)) {
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;
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
if (instruction.inputs[1].isMutableIndexable(compiler)) {
if (!instruction.inputs[2].isInteger() && compiler.enableTypeAssertions) {
// We want the right checked mode error.
return null;
}
return new HIndexAssign(instruction.inputs[1],
instruction.inputs[2],
instruction.inputs[3],
instruction.selector);
}
return null;
}
}
class IndexSpecializer extends InvokeDynamicSpecializer {
const IndexSpecializer();
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
HInstruction index = instruction.inputs[2];
if (input == instruction.inputs[1] &&
index.instructionType.canBePrimitiveNumber(compiler)) {
return HType.INDEXABLE_PRIMITIVE;
}
// 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;
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
if (instruction.inputs[1].isIndexable(compiler)) {
if (!instruction.inputs[2].isInteger() && compiler.enableTypeAssertions) {
// We want the right checked mode error.
return null;
}
HInstruction index = new HIndex(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
index.instructionType =
new HType.inferredTypeForSelector(instruction.selector, compiler);
return index;
}
return null;
}
}
class BitNotSpecializer extends InvokeDynamicSpecializer {
const BitNotSpecializer();
UnaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.bitNot;
}
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
if (input == instruction.inputs[1]) {
HType propagatedType = instruction.instructionType;
if (propagatedType.canBePrimitiveNumber(compiler)) {
return HType.INTEGER;
}
}
return HType.UNKNOWN;
}
HType computeTypeFromInputTypes(HInvokeDynamic instruction,
Compiler compiler) {
// All bitwise operations on primitive types either produce an
// integer or throw an error.
if (instruction.inputs[1].isPrimitiveOrNull()) return HType.INTEGER;
return super.computeTypeFromInputTypes(instruction, compiler);
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
HInstruction input = instruction.inputs[1];
if (input.isNumber()) return new HBitNot(input, instruction.selector);
return null;
}
}
class UnaryNegateSpecializer extends InvokeDynamicSpecializer {
const UnaryNegateSpecializer();
UnaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.negate;
}
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
if (input == instruction.inputs[1]) {
HType propagatedType = instruction.instructionType;
// 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.canBePrimitiveNumber(compiler)) return HType.NUMBER;
}
return HType.UNKNOWN;
}
HType computeTypeFromInputTypes(HInvokeDynamic instruction,
Compiler compiler) {
HType operandType = instruction.inputs[1].instructionType;
if (operandType.isNumberOrNull()) return operandType;
return super.computeTypeFromInputTypes(instruction, compiler);
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
HInstruction input = instruction.inputs[1];
if (input.isNumber()) return new HNegate(input, instruction.selector);
return null;
}
}
abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer {
const BinaryArithmeticSpecializer();
HType computeTypeFromInputTypes(HInvokeDynamic instruction,
Compiler compiler) {
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
if (left.isIntegerOrNull() && right.isIntegerOrNull()) return HType.INTEGER;
if (left.isNumberOrNull()) {
if (left.isDoubleOrNull() || right.isDoubleOrNull()) return HType.DOUBLE;
return HType.NUMBER;
}
return super.computeTypeFromInputTypes(instruction, compiler);
}
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
if (input == instruction.inputs[0]) return HType.UNKNOWN;
HType propagatedType = instruction.instructionType;
// 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.canBePrimitiveNumber(compiler)) {
return HType.NUMBER;
}
// Even if the desired outgoing type is not a number we still want the
// second argument to be a number if the first one is a number. This will
// not help for the outgoing type, but at least the binary arithmetic
// operation will not have type problems.
// TODO(floitsch): normally we shouldn't request a number, but simply
// throw an ArgumentError if it isn't. This would be similar
// to the array case.
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
if (input == right && left.isNumber()) return HType.NUMBER;
return HType.UNKNOWN;
}
bool isBuiltin(HInvokeDynamic instruction) {
return instruction.inputs[1].isNumber()
&& instruction.inputs[2].isNumber();
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
if (isBuiltin(instruction)) {
HInstruction builtin = newBuiltinVariant(instruction);
if (builtin != null) return builtin;
// Even if there is no builtin equivalent instruction, we know
// the instruction does not have any side effect, and that it
// can be GVN'ed.
instruction.sideEffects.clearAllSideEffects();
instruction.sideEffects.clearAllDependencies();
instruction.setUseGvn();
}
return null;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction);
}
class AddSpecializer extends BinaryArithmeticSpecializer {
const AddSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.add;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HAdd(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class DivideSpecializer extends BinaryArithmeticSpecializer {
const DivideSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.divide;
}
HType computeTypeFromInputTypes(HInstruction instruction,
Compiler compiler) {
HInstruction left = instruction.inputs[1];
if (left.isNumberOrNull()) return HType.DOUBLE;
return super.computeTypeFromInputTypes(instruction, compiler);
}
HType computeDesiredTypeForInput(HInstruction instruction,
HInstruction input,
Compiler compiler) {
if (input == instruction.inputs[0]) return HType.UNKNOWN;
// A division can never return an integer. So don't ask for integer inputs.
if (instruction.isInteger()) return HType.UNKNOWN;
return super.computeDesiredTypeForInput(
instruction, input, compiler);
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HDivide(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class ModuloSpecializer extends BinaryArithmeticSpecializer {
const ModuloSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.modulo;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
// Modulo cannot be mapped to the native operator (different semantics).
return null;
}
}
class MultiplySpecializer extends BinaryArithmeticSpecializer {
const MultiplySpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.multiply;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HMultiply(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class SubtractSpecializer extends BinaryArithmeticSpecializer {
const SubtractSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.subtract;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HSubtract(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer {
const TruncatingDivideSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.truncatingDivide;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
// Truncating divide does not have a JS equivalent.
return null;
}
}
abstract class BinaryBitOpSpecializer extends BinaryArithmeticSpecializer {
const BinaryBitOpSpecializer();
HType computeTypeFromInputTypes(HInvokeDynamic instruction,
Compiler compiler) {
// All bitwise operations on primitive types either produce an
// integer or throw an error.
HInstruction left = instruction.inputs[1];
if (left.isPrimitiveOrNull()) return HType.INTEGER;
return super.computeTypeFromInputTypes(instruction, compiler);
}
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
if (input == instruction.inputs[0]) return HType.UNKNOWN;
// We match the implementation of bit operations on the
// [:JSNumber:] class by requesting a number if the receiver can
// be a number.
HInstruction left = instruction.inputs[1];
if (left.instructionType.canBePrimitiveNumber(compiler)) {
return HType.NUMBER;
}
return HType.UNKNOWN;
}
}
class ShiftLeftSpecializer extends BinaryBitOpSpecializer {
const ShiftLeftSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.shiftLeft;
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
if (!left.isNumber() || !right.isConstantInteger()) return null;
HConstant rightConstant = right;
IntConstant intConstant = rightConstant.constant;
int count = intConstant.value;
if (count >= 0 && count <= 31) {
return newBuiltinVariant(instruction);
}
return null;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HShiftLeft(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class ShiftRightSpecializer extends BinaryBitOpSpecializer {
const ShiftRightSpecializer();
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
// Shift right cannot be mapped to the native operator easily.
return null;
}
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.shiftRight;
}
}
class BitOrSpecializer extends BinaryBitOpSpecializer {
const BitOrSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.bitOr;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HBitOr(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class BitAndSpecializer extends BinaryBitOpSpecializer {
const BitAndSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.bitAnd;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HBitAnd(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class BitXorSpecializer extends BinaryBitOpSpecializer {
const BitXorSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.bitXor;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HBitXor(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
abstract class RelationalSpecializer extends InvokeDynamicSpecializer {
const RelationalSpecializer();
HType computeTypeFromInputTypes(HInvokeDynamic instruction,
Compiler compiler) {
if (instruction.inputs[1].instructionType.isPrimitiveOrNull()) {
return HType.BOOLEAN;
}
return super.computeTypeFromInputTypes(instruction, compiler);
}
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
if (input == instruction.inputs[0]) return HType.UNKNOWN;
HType propagatedType = instruction.instructionType;
// For all relational operations except HIdentity, 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.canBePrimitiveBoolean(compiler)) {
HInstruction left = instruction.inputs[1];
if (left.instructionType.canBePrimitiveNumber(compiler)) {
return HType.NUMBER;
}
}
return HType.UNKNOWN;
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
if (left.isNumber() && right.isNumber()) {
return newBuiltinVariant(instruction);
}
return null;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction);
}
class EqualsSpecializer extends RelationalSpecializer {
const EqualsSpecializer();
HType computeDesiredTypeForInput(HInvokeDynamic instruction,
HInstruction input,
Compiler compiler) {
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
if (input == left && right.instructionType.isUseful()) {
// All our useful types have 'identical' 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 is a number we don't need more than a number (no need to match
// the exact type of right).
if (right.isNumber()) return HType.NUMBER;
return right.instructionType;
}
// String equality testing is much more common than array equality testing.
if (input == left && left.isIndexablePrimitive(compiler)) {
return HType.READABLE_ARRAY;
}
// String equality testing is much more common than array equality testing.
if (input == right && right.isIndexablePrimitive(compiler)) {
return HType.STRING;
}
return HType.UNKNOWN;
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
HType instructionType = left.instructionType;
if (right.isConstantNull() || instructionType.isPrimitiveOrNull()) {
return newBuiltinVariant(instruction);
}
Selector selector = instructionType.refine(instruction.selector, compiler);
World world = compiler.world;
JavaScriptBackend backend = compiler.backend;
Iterable<Element> matches = world.allFunctions.filter(selector);
// This test relies the on `Object.==` and `Interceptor.==` always being
// implemented because if the selector matches by subtype, it still will be
// a regular object or an interceptor.
if (matches.every(backend.isDefaultEqualityImplementation)) {
return newBuiltinVariant(instruction);
}
return null;
}
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.equal;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HIdentity(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class LessSpecializer extends RelationalSpecializer {
const LessSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.less;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HLess(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class GreaterSpecializer extends RelationalSpecializer {
const GreaterSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.greater;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HGreater(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class GreaterEqualSpecializer extends RelationalSpecializer {
const GreaterEqualSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.greaterEqual;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HGreaterEqual(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}
class LessEqualSpecializer extends RelationalSpecializer {
const LessEqualSpecializer();
BinaryOperation operation(ConstantSystem constantSystem) {
return constantSystem.lessEqual;
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction) {
return new HLessEqual(
instruction.inputs[1], instruction.inputs[2], instruction.selector);
}
}