// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_COMPILER_REPRESENTATION_CHANGE_H_
#define V8_COMPILER_REPRESENTATION_CHANGE_H_

#include <sstream>

#include "src/base/bits.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/simplified-operator.h"

namespace v8 {
namespace internal {
namespace compiler {

// Contains logic related to changing the representation of values for constants
// and other nodes, as well as lowering Simplified->Machine operators.
// Eagerly folds any representation changes for constants.
class RepresentationChanger {
 public:
  RepresentationChanger(JSGraph* jsgraph, SimplifiedOperatorBuilder* simplified,
                        Isolate* isolate)
      : jsgraph_(jsgraph),
        simplified_(simplified),
        isolate_(isolate),
        testing_type_errors_(false),
        type_error_(false) {}

  // TODO(titzer): should Word64 also be implicitly convertable to others?
  static const MachineTypeUnion rWord = kRepWord8 | kRepWord16 | kRepWord32;

  Node* GetRepresentationFor(Node* node, MachineTypeUnion output_type,
                             MachineTypeUnion use_type) {
    if (!base::bits::IsPowerOfTwo32(output_type & kRepMask)) {
      // There should be only one output representation.
      return TypeError(node, output_type, use_type);
    }
    if ((use_type & kRepMask) == (output_type & kRepMask)) {
      // Representations are the same. That's a no-op.
      return node;
    }
    if ((use_type & rWord) && (output_type & rWord)) {
      // Both are words less than or equal to 32-bits.
      // Since loads of integers from memory implicitly sign or zero extend the
      // value to the full machine word size and stores implicitly truncate,
      // no representation change is necessary.
      return node;
    }
    if (use_type & kRepTagged) {
      return GetTaggedRepresentationFor(node, output_type);
    } else if (use_type & kRepFloat32) {
      return GetFloat32RepresentationFor(node, output_type);
    } else if (use_type & kRepFloat64) {
      return GetFloat64RepresentationFor(node, output_type);
    } else if (use_type & kRepBit) {
      return GetBitRepresentationFor(node, output_type);
    } else if (use_type & rWord) {
      return GetWord32RepresentationFor(node, output_type,
                                        use_type & kTypeUint32);
    } else if (use_type & kRepWord64) {
      return GetWord64RepresentationFor(node, output_type);
    } else {
      return node;
    }
  }

  Node* GetTaggedRepresentationFor(Node* node, MachineTypeUnion output_type) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kNumberConstant:
      case IrOpcode::kHeapConstant:
        return node;  // No change necessary.
      case IrOpcode::kInt32Constant:
        if (output_type & kTypeUint32) {
          uint32_t value = OpParameter<uint32_t>(node);
          return jsgraph()->Constant(static_cast<double>(value));
        } else if (output_type & kTypeInt32) {
          int32_t value = OpParameter<int32_t>(node);
          return jsgraph()->Constant(value);
        } else if (output_type & kRepBit) {
          return OpParameter<int32_t>(node) == 0 ? jsgraph()->FalseConstant()
                                                 : jsgraph()->TrueConstant();
        } else {
          return TypeError(node, output_type, kRepTagged);
        }
      case IrOpcode::kFloat64Constant:
        return jsgraph()->Constant(OpParameter<double>(node));
      case IrOpcode::kFloat32Constant:
        return jsgraph()->Constant(OpParameter<float>(node));
      default:
        break;
    }
    // Select the correct X -> Tagged operator.
    const Operator* op;
    if (output_type & kRepBit) {
      op = simplified()->ChangeBitToBool();
    } else if (output_type & rWord) {
      if (output_type & kTypeUint32) {
        op = simplified()->ChangeUint32ToTagged();
      } else if (output_type & kTypeInt32) {
        op = simplified()->ChangeInt32ToTagged();
      } else {
        return TypeError(node, output_type, kRepTagged);
      }
    } else if (output_type & kRepFloat32) {  // float32 -> float64 -> tagged
      node = InsertChangeFloat32ToFloat64(node);
      op = simplified()->ChangeFloat64ToTagged();
    } else if (output_type & kRepFloat64) {
      op = simplified()->ChangeFloat64ToTagged();
    } else {
      return TypeError(node, output_type, kRepTagged);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetFloat32RepresentationFor(Node* node, MachineTypeUnion output_type) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kFloat64Constant:
      case IrOpcode::kNumberConstant:
        return jsgraph()->Float32Constant(
            DoubleToFloat32(OpParameter<double>(node)));
      case IrOpcode::kInt32Constant:
        if (output_type & kTypeUint32) {
          uint32_t value = OpParameter<uint32_t>(node);
          return jsgraph()->Float32Constant(static_cast<float>(value));
        } else {
          int32_t value = OpParameter<int32_t>(node);
          return jsgraph()->Float32Constant(static_cast<float>(value));
        }
      case IrOpcode::kFloat32Constant:
        return node;  // No change necessary.
      default:
        break;
    }
    // Select the correct X -> Float32 operator.
    const Operator* op;
    if (output_type & kRepBit) {
      return TypeError(node, output_type, kRepFloat32);
    } else if (output_type & rWord) {
      if (output_type & kTypeUint32) {
        op = machine()->ChangeUint32ToFloat64();
      } else {
        op = machine()->ChangeInt32ToFloat64();
      }
      // int32 -> float64 -> float32
      node = jsgraph()->graph()->NewNode(op, node);
      op = machine()->TruncateFloat64ToFloat32();
    } else if (output_type & kRepTagged) {
      op = simplified()
               ->ChangeTaggedToFloat64();  // tagged -> float64 -> float32
      node = jsgraph()->graph()->NewNode(op, node);
      op = machine()->TruncateFloat64ToFloat32();
    } else if (output_type & kRepFloat64) {
      op = machine()->TruncateFloat64ToFloat32();
    } else {
      return TypeError(node, output_type, kRepFloat32);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetFloat64RepresentationFor(Node* node, MachineTypeUnion output_type) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kNumberConstant:
        return jsgraph()->Float64Constant(OpParameter<double>(node));
      case IrOpcode::kInt32Constant:
        if (output_type & kTypeUint32) {
          uint32_t value = OpParameter<uint32_t>(node);
          return jsgraph()->Float64Constant(static_cast<double>(value));
        } else {
          int32_t value = OpParameter<int32_t>(node);
          return jsgraph()->Float64Constant(value);
        }
      case IrOpcode::kFloat64Constant:
        return node;  // No change necessary.
      case IrOpcode::kFloat32Constant:
        return jsgraph()->Float64Constant(OpParameter<float>(node));
      default:
        break;
    }
    // Select the correct X -> Float64 operator.
    const Operator* op;
    if (output_type & kRepBit) {
      return TypeError(node, output_type, kRepFloat64);
    } else if (output_type & rWord) {
      if (output_type & kTypeUint32) {
        op = machine()->ChangeUint32ToFloat64();
      } else {
        op = machine()->ChangeInt32ToFloat64();
      }
    } else if (output_type & kRepTagged) {
      op = simplified()->ChangeTaggedToFloat64();
    } else if (output_type & kRepFloat32) {
      op = machine()->ChangeFloat32ToFloat64();
    } else {
      return TypeError(node, output_type, kRepFloat64);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* MakeInt32Constant(double value) {
    if (value < 0) {
      DCHECK(IsInt32Double(value));
      int32_t iv = static_cast<int32_t>(value);
      return jsgraph()->Int32Constant(iv);
    } else {
      DCHECK(IsUint32Double(value));
      int32_t iv = static_cast<int32_t>(static_cast<uint32_t>(value));
      return jsgraph()->Int32Constant(iv);
    }
  }

  Node* GetTruncatedWord32For(Node* node, MachineTypeUnion output_type) {
    // Eagerly fold truncations for constants.
    switch (node->opcode()) {
      case IrOpcode::kInt32Constant:
        return node;  // No change necessary.
      case IrOpcode::kFloat32Constant:
        return jsgraph()->Int32Constant(
            DoubleToInt32(OpParameter<float>(node)));
      case IrOpcode::kNumberConstant:
      case IrOpcode::kFloat64Constant:
        return jsgraph()->Int32Constant(
            DoubleToInt32(OpParameter<double>(node)));
      default:
        break;
    }
    // Select the correct X -> Word32 truncation operator.
    const Operator* op = NULL;
    if (output_type & kRepFloat64) {
      op = machine()->TruncateFloat64ToInt32(TruncationMode::kJavaScript);
    } else if (output_type & kRepFloat32) {
      node = InsertChangeFloat32ToFloat64(node);
      op = machine()->TruncateFloat64ToInt32(TruncationMode::kJavaScript);
    } else if (output_type & kRepTagged) {
      node = InsertChangeTaggedToFloat64(node);
      op = machine()->TruncateFloat64ToInt32(TruncationMode::kJavaScript);
    } else {
      return TypeError(node, output_type, kRepWord32);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetWord32RepresentationFor(Node* node, MachineTypeUnion output_type,
                                   bool use_unsigned) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kInt32Constant:
        return node;  // No change necessary.
      case IrOpcode::kFloat32Constant:
        return MakeInt32Constant(OpParameter<float>(node));
      case IrOpcode::kNumberConstant:
      case IrOpcode::kFloat64Constant:
        return MakeInt32Constant(OpParameter<double>(node));
      default:
        break;
    }
    // Select the correct X -> Word32 operator.
    const Operator* op;
    if (output_type & kRepBit) {
      return node;  // Sloppy comparison -> word32
    } else if (output_type & kRepFloat64) {
      if (output_type & kTypeUint32 || use_unsigned) {
        op = machine()->ChangeFloat64ToUint32();
      } else {
        op = machine()->ChangeFloat64ToInt32();
      }
    } else if (output_type & kRepFloat32) {
      node = InsertChangeFloat32ToFloat64(node);  // float32 -> float64 -> int32
      if (output_type & kTypeUint32 || use_unsigned) {
        op = machine()->ChangeFloat64ToUint32();
      } else {
        op = machine()->ChangeFloat64ToInt32();
      }
    } else if (output_type & kRepTagged) {
      if (output_type & kTypeUint32 || use_unsigned) {
        op = simplified()->ChangeTaggedToUint32();
      } else {
        op = simplified()->ChangeTaggedToInt32();
      }
    } else {
      return TypeError(node, output_type, kRepWord32);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetBitRepresentationFor(Node* node, MachineTypeUnion output_type) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kHeapConstant: {
        Handle<Object> value = OpParameter<Unique<Object> >(node).handle();
        DCHECK(value.is_identical_to(factory()->true_value()) ||
               value.is_identical_to(factory()->false_value()));
        return jsgraph()->Int32Constant(
            value.is_identical_to(factory()->true_value()) ? 1 : 0);
      }
      default:
        break;
    }
    // Select the correct X -> Bit operator.
    const Operator* op;
    if (output_type & kRepTagged) {
      op = simplified()->ChangeBoolToBit();
    } else {
      return TypeError(node, output_type, kRepBit);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetWord64RepresentationFor(Node* node, MachineTypeUnion output_type) {
    if (output_type & kRepBit) {
      return node;  // Sloppy comparison -> word64
    }
    // Can't really convert Word64 to anything else. Purported to be internal.
    return TypeError(node, output_type, kRepWord64);
  }

  const Operator* Int32OperatorFor(IrOpcode::Value opcode) {
    switch (opcode) {
      case IrOpcode::kNumberAdd:
        return machine()->Int32Add();
      case IrOpcode::kNumberSubtract:
        return machine()->Int32Sub();
      case IrOpcode::kNumberMultiply:
        return machine()->Int32Mul();
      case IrOpcode::kNumberDivide:
        return machine()->Int32Div();
      case IrOpcode::kNumberModulus:
        return machine()->Int32Mod();
      case IrOpcode::kNumberEqual:
        return machine()->Word32Equal();
      case IrOpcode::kNumberLessThan:
        return machine()->Int32LessThan();
      case IrOpcode::kNumberLessThanOrEqual:
        return machine()->Int32LessThanOrEqual();
      default:
        UNREACHABLE();
        return NULL;
    }
  }

  const Operator* Uint32OperatorFor(IrOpcode::Value opcode) {
    switch (opcode) {
      case IrOpcode::kNumberAdd:
        return machine()->Int32Add();
      case IrOpcode::kNumberSubtract:
        return machine()->Int32Sub();
      case IrOpcode::kNumberMultiply:
        return machine()->Int32Mul();
      case IrOpcode::kNumberDivide:
        return machine()->Uint32Div();
      case IrOpcode::kNumberModulus:
        return machine()->Uint32Mod();
      case IrOpcode::kNumberEqual:
        return machine()->Word32Equal();
      case IrOpcode::kNumberLessThan:
        return machine()->Uint32LessThan();
      case IrOpcode::kNumberLessThanOrEqual:
        return machine()->Uint32LessThanOrEqual();
      default:
        UNREACHABLE();
        return NULL;
    }
  }

  const Operator* Float64OperatorFor(IrOpcode::Value opcode) {
    switch (opcode) {
      case IrOpcode::kNumberAdd:
        return machine()->Float64Add();
      case IrOpcode::kNumberSubtract:
        return machine()->Float64Sub();
      case IrOpcode::kNumberMultiply:
        return machine()->Float64Mul();
      case IrOpcode::kNumberDivide:
        return machine()->Float64Div();
      case IrOpcode::kNumberModulus:
        return machine()->Float64Mod();
      case IrOpcode::kNumberEqual:
        return machine()->Float64Equal();
      case IrOpcode::kNumberLessThan:
        return machine()->Float64LessThan();
      case IrOpcode::kNumberLessThanOrEqual:
        return machine()->Float64LessThanOrEqual();
      default:
        UNREACHABLE();
        return NULL;
    }
  }

  MachineType TypeForBasePointer(const FieldAccess& access) {
    return access.tag() != 0 ? kMachAnyTagged : kMachPtr;
  }

  MachineType TypeForBasePointer(const ElementAccess& access) {
    return access.tag() != 0 ? kMachAnyTagged : kMachPtr;
  }

  MachineType TypeFromUpperBound(Type* type) {
    if (type->Is(Type::None()))
      return kTypeAny;  // TODO(titzer): should be an error
    if (type->Is(Type::Signed32())) return kTypeInt32;
    if (type->Is(Type::Unsigned32())) return kTypeUint32;
    if (type->Is(Type::Number())) return kTypeNumber;
    if (type->Is(Type::Boolean())) return kTypeBool;
    return kTypeAny;
  }

 private:
  JSGraph* jsgraph_;
  SimplifiedOperatorBuilder* simplified_;
  Isolate* isolate_;

  friend class RepresentationChangerTester;  // accesses the below fields.

  bool testing_type_errors_;  // If {true}, don't abort on a type error.
  bool type_error_;           // Set when a type error is detected.

  Node* TypeError(Node* node, MachineTypeUnion output_type,
                  MachineTypeUnion use) {
    type_error_ = true;
    if (!testing_type_errors_) {
      std::ostringstream out_str;
      out_str << static_cast<MachineType>(output_type);

      std::ostringstream use_str;
      use_str << static_cast<MachineType>(use);

      V8_Fatal(__FILE__, __LINE__,
               "RepresentationChangerError: node #%d:%s of "
               "%s cannot be changed to %s",
               node->id(), node->op()->mnemonic(), out_str.str().c_str(),
               use_str.str().c_str());
    }
    return node;
  }

  Node* InsertChangeFloat32ToFloat64(Node* node) {
    return jsgraph()->graph()->NewNode(machine()->ChangeFloat32ToFloat64(),
                                       node);
  }

  Node* InsertChangeTaggedToFloat64(Node* node) {
    return jsgraph()->graph()->NewNode(simplified()->ChangeTaggedToFloat64(),
                                       node);
  }

  JSGraph* jsgraph() const { return jsgraph_; }
  Isolate* isolate() const { return isolate_; }
  Factory* factory() const { return isolate()->factory(); }
  SimplifiedOperatorBuilder* simplified() { return simplified_; }
  MachineOperatorBuilder* machine() { return jsgraph()->machine(); }
};

}  // namespace compiler
}  // namespace internal
}  // namespace v8

#endif  // V8_COMPILER_REPRESENTATION_CHANGE_H_
