content: // 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();
  
    TypeMask computeTypeFromInputTypes(HInvokeDynamic instruction,
                                       Compiler compiler) {
      Selector selector = instruction.selector;
      TypeMask type = TypeMaskFactory.inferredTypeForSelector(selector, 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(selector);
      }
      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 IndexSpecializer()
            : const IndexAssignSpecializer();
      } else if (selector.kind == SelectorKind.OPERATOR) {
        if (selector.name == 'unary-') {
          return const UnaryNegateSpecializer();
        } else if (selector.name == '~') {
          return const BitNotSpecializer();
        } else if (selector.name == '+') {
          return const AddSpecializer();
        } else if (selector.name == '-') {
          return const SubtractSpecializer();
        } else if (selector.name == '*') {
          return const MultiplySpecializer();
        } else if (selector.name == '/') {
          return const DivideSpecializer();
        } else if (selector.name == '~/') {
          return const TruncatingDivideSpecializer();
        } else if (selector.name == '%') {
          return const ModuloSpecializer();
        } else if (selector.name == '>>') {
          return const ShiftRightSpecializer();
        } else if (selector.name == '<<') {
          return const ShiftLeftSpecializer();
        } else if (selector.name == '&') {
          return const BitAndSpecializer();
        } else if (selector.name == '|') {
          return const BitOrSpecializer();
        } else if (selector.name == '^') {
          return const BitXorSpecializer();
        } else if (selector.name == '==') {
          return const EqualsSpecializer();
        } else if (selector.name == '<') {
          return const LessSpecializer();
        } else if (selector.name == '<=') {
          return const LessEqualSpecializer();
        } else if (selector.name == '>') {
          return const GreaterSpecializer();
        } else if (selector.name == '>=') {
          return const GreaterEqualSpecializer();
        }
      }
      return const InvokeDynamicSpecializer();
    }
  }
  
  class IndexAssignSpecializer extends InvokeDynamicSpecializer {
    const IndexAssignSpecializer();
  
    HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
                                     Compiler compiler) {
      if (instruction.inputs[1].isMutableIndexable(compiler)) {
        if (!instruction.inputs[2].isInteger(compiler)
            && 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();
  
    HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
                                     Compiler compiler) {
      if (!instruction.inputs[1].isIndexablePrimitive(compiler)) return null;
      if (!instruction.inputs[2].isInteger(compiler)
          && compiler.enableTypeAssertions) {
        // We want the right checked mode error.
        return null;
      }
      TypeMask receiverType =
          instruction.getDartReceiver(compiler).instructionType;
      Selector refined = new TypedSelector(receiverType, instruction.selector);
      TypeMask type = TypeMaskFactory.inferredTypeForSelector(refined, compiler);
      return new HIndex(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, type);
    }
  }
  
  class BitNotSpecializer extends InvokeDynamicSpecializer {
    const BitNotSpecializer();
  
    UnaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.bitNot;
    }
  
    TypeMask computeTypeFromInputTypes(HInvokeDynamic instruction,
                                       Compiler compiler) {
      // All bitwise operations on primitive types either produce an
      // integer or throw an error.
      JavaScriptBackend backend = compiler.backend;
      if (instruction.inputs[1].isPrimitiveOrNull(compiler)) {
        return backend.intType;
      }
      return super.computeTypeFromInputTypes(instruction, compiler);
    }
  
    HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
                                     Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      HInstruction input = instruction.inputs[1];
      if (input.isNumber(compiler)) {
        return new HBitNot(input, instruction.selector, backend.intType);
      }
      return null;
    }
  }
  
  class UnaryNegateSpecializer extends InvokeDynamicSpecializer {
    const UnaryNegateSpecializer();
  
    UnaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.negate;
    }
  
    TypeMask computeTypeFromInputTypes(HInvokeDynamic instruction,
                                       Compiler compiler) {
      TypeMask operandType = instruction.inputs[1].instructionType;
      if (instruction.inputs[1].isNumberOrNull(compiler)) return operandType;
      return super.computeTypeFromInputTypes(instruction, compiler);
    }
  
    HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
                                     Compiler compiler) {
      HInstruction input = instruction.inputs[1];
      if (input.isNumber(compiler)) {
        return new HNegate(input, instruction.selector, input.instructionType);
      }
      return null;
    }
  }
  
  abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer {
    const BinaryArithmeticSpecializer();
  
    TypeMask computeTypeFromInputTypes(HInvokeDynamic instruction,
                                       Compiler compiler) {
      HInstruction left = instruction.inputs[1];
      HInstruction right = instruction.inputs[2];
      JavaScriptBackend backend = compiler.backend;
      if (left.isIntegerOrNull(compiler) && right.isIntegerOrNull(compiler)) {
        return backend.intType;
      }
      if (left.isNumberOrNull(compiler)) {
        if (left.isDoubleOrNull(compiler) || right.isDoubleOrNull(compiler)) {
          return backend.doubleType;
        }
        return backend.numType;
      }
      return super.computeTypeFromInputTypes(instruction, compiler);
    }
  
    bool isBuiltin(HInvokeDynamic instruction, Compiler compiler) {
      return instruction.inputs[1].isNumber(compiler)
          && instruction.inputs[2].isNumber(compiler);
    }
  
    HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
                                     Compiler compiler) {
      if (isBuiltin(instruction, compiler)) {
        HInstruction builtin = newBuiltinVariant(instruction, compiler);
        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, Compiler compiler);
  }
  
  class AddSpecializer extends BinaryArithmeticSpecializer {
    const AddSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.add;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      return new HAdd(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, computeTypeFromInputTypes(instruction, compiler));
    }
  }
  
  class DivideSpecializer extends BinaryArithmeticSpecializer {
    const DivideSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.divide;
    }
  
    TypeMask computeTypeFromInputTypes(HInstruction instruction,
                                       Compiler compiler) {
      HInstruction left = instruction.inputs[1];
      JavaScriptBackend backend = compiler.backend;
      if (left.isNumberOrNull(compiler)) {
        return backend.doubleType;
      }
      return super.computeTypeFromInputTypes(instruction, compiler);
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HDivide(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.doubleType);
    }
  }
  
  class ModuloSpecializer extends BinaryArithmeticSpecializer {
    const ModuloSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.modulo;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      // 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,
                                   Compiler compiler) {
      return new HMultiply(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, computeTypeFromInputTypes(instruction, compiler));
    }
  }
  
  class SubtractSpecializer extends BinaryArithmeticSpecializer {
    const SubtractSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.subtract;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      return new HSubtract(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, computeTypeFromInputTypes(instruction, compiler));
    }
  }
  
  class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer {
    const TruncatingDivideSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.truncatingDivide;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      // Truncating divide does not have a JS equivalent.    
      return null;
    }
  }
  
  abstract class BinaryBitOpSpecializer extends BinaryArithmeticSpecializer {
    const BinaryBitOpSpecializer();
  
    TypeMask computeTypeFromInputTypes(HInvokeDynamic instruction,
                                       Compiler compiler) {
      // All bitwise operations on primitive types either produce an
      // integer or throw an error.
      HInstruction left = instruction.inputs[1];
      JavaScriptBackend backend = compiler.backend;
      if (left.isPrimitiveOrNull(compiler)) {
        return backend.intType;
      }
      return super.computeTypeFromInputTypes(instruction, compiler);
    }
  }
  
  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(compiler)) return null;
      if (argumentLessThan32(right)) {
        return newBuiltinVariant(instruction, compiler);
      }
      return null;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HShiftLeft(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.intType);
    }
  
    bool argumentLessThan32(HInstruction instruction) {
      if (!instruction.isConstantInteger()) return false;
      HConstant rightConstant = instruction;
      IntConstant intConstant = rightConstant.constant;
      int count = intConstant.value;
      return count >= 0 && count <= 31;
    }
  }
  
  class ShiftRightSpecializer extends BinaryBitOpSpecializer {
    const ShiftRightSpecializer();
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      // 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,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HBitOr(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.intType);
    }
  }
  
  class BitAndSpecializer extends BinaryBitOpSpecializer {
    const BitAndSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.bitAnd;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HBitAnd(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.intType);
    }
  }
  
  class BitXorSpecializer extends BinaryBitOpSpecializer {
    const BitXorSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.bitXor;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HBitXor(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.intType);
    }
  }
  
  abstract class RelationalSpecializer extends InvokeDynamicSpecializer {
    const RelationalSpecializer();
  
    TypeMask computeTypeFromInputTypes(HInvokeDynamic instruction,
                                       Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      if (instruction.inputs[1].isPrimitiveOrNull(compiler)) {
        return backend.boolType;
      }
      return super.computeTypeFromInputTypes(instruction, compiler);
    }
  
    HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
                                     Compiler compiler) {
      HInstruction left = instruction.inputs[1];
      HInstruction right = instruction.inputs[2];
      if (left.isNumber(compiler) && right.isNumber(compiler)) {
        return newBuiltinVariant(instruction, compiler);
      }
      return null;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction, Compiler compiler);
  }
  
  class EqualsSpecializer extends RelationalSpecializer {
    const EqualsSpecializer();
  
    HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
                                     Compiler compiler) {
      HInstruction left = instruction.inputs[1];
      HInstruction right = instruction.inputs[2];
      TypeMask instructionType = left.instructionType;
      if (right.isConstantNull() || left.isPrimitiveOrNull(compiler)) {
        return newBuiltinVariant(instruction, compiler);
      }
      Selector selector =
          new TypedSelector(instructionType, instruction.selector);
      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, compiler);
      }
      return null;
    }
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.equal;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HIdentity(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.boolType);
    }
  }
  
  class LessSpecializer extends RelationalSpecializer {
    const LessSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.less;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HLess(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.boolType);
    }
  }
  
  class GreaterSpecializer extends RelationalSpecializer {
    const GreaterSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.greater;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HGreater(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.boolType);
    }
  }
  
  class GreaterEqualSpecializer extends RelationalSpecializer {
    const GreaterEqualSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.greaterEqual;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HGreaterEqual(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.boolType);
    }
  }
  
  class LessEqualSpecializer extends RelationalSpecializer {
    const LessEqualSpecializer();
  
    BinaryOperation operation(ConstantSystem constantSystem) {
      return constantSystem.lessEqual;
    }
  
    HInstruction newBuiltinVariant(HInvokeDynamic instruction,
                                   Compiler compiler) {
      JavaScriptBackend backend = compiler.backend;
      return new HLessEqual(
          instruction.inputs[1], instruction.inputs[2],
          instruction.selector, backend.boolType);
    }
  }
  