// 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 "src/compiler/js-graph.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node-properties-inl.h"
#include "src/compiler/simplified-operator.h"

namespace v8 {
namespace internal {
namespace compiler {

// The types and representations tracked during representation inference
// and change insertion.
// TODO(titzer): First, merge MachineRepresentation and RepType.
// TODO(titzer): Second, Use the real type system instead of RepType.
enum RepType {
  // Representations.
  rBit = 1 << 0,
  rWord32 = 1 << 1,
  rWord64 = 1 << 2,
  rFloat64 = 1 << 3,
  rTagged = 1 << 4,

  // Types.
  tBool = 1 << 5,
  tInt32 = 1 << 6,
  tUint32 = 1 << 7,
  tInt64 = 1 << 8,
  tUint64 = 1 << 9,
  tNumber = 1 << 10,
  tAny = 1 << 11
};

typedef uint16_t RepTypeUnion;

const RepTypeUnion rMask = rBit | rWord32 | rWord64 | rFloat64 | rTagged;
const RepTypeUnion tMask =
    tBool | tInt32 | tUint32 | tInt64 | tUint64 | tNumber | tAny;
const RepType rPtr = kPointerSize == 4 ? rWord32 : rWord64;


// 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,
                        MachineOperatorBuilder* machine, Isolate* isolate)
      : jsgraph_(jsgraph),
        simplified_(simplified),
        machine_(machine),
        isolate_(isolate),
        testing_type_errors_(false),
        type_error_(false) {}


  Node* GetRepresentationFor(Node* node, RepTypeUnion output_type,
                             RepTypeUnion use_type) {
    if (!IsPowerOf2(output_type & rMask)) {
      // There should be only one output representation.
      return TypeError(node, output_type, use_type);
    }
    if ((use_type & rMask) == (output_type & rMask)) {
      // Representations are the same. That's a no-op.
      return node;
    }
    if (use_type & rTagged) {
      return GetTaggedRepresentationFor(node, output_type);
    } else if (use_type & rFloat64) {
      return GetFloat64RepresentationFor(node, output_type);
    } else if (use_type & rWord32) {
      return GetWord32RepresentationFor(node, output_type);
    } else if (use_type & rBit) {
      return GetBitRepresentationFor(node, output_type);
    } else if (use_type & rWord64) {
      return GetWord64RepresentationFor(node, output_type);
    } else {
      return node;
    }
  }

  Node* GetTaggedRepresentationFor(Node* node, RepTypeUnion 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 & tUint32) {
          uint32_t value = ValueOf<uint32_t>(node->op());
          return jsgraph()->Constant(static_cast<double>(value));
        } else if (output_type & tInt32) {
          int32_t value = ValueOf<int32_t>(node->op());
          return jsgraph()->Constant(value);
        } else if (output_type & rBit) {
          return ValueOf<int32_t>(node->op()) == 0 ? jsgraph()->FalseConstant()
                                                   : jsgraph()->TrueConstant();
        } else {
          return TypeError(node, output_type, rTagged);
        }
      case IrOpcode::kFloat64Constant:
        return jsgraph()->Constant(ValueOf<double>(node->op()));
      default:
        break;
    }
    // Select the correct X -> Tagged operator.
    Operator* op;
    if (output_type & rBit) {
      op = simplified()->ChangeBitToBool();
    } else if (output_type & rWord32) {
      if (output_type & tUint32) {
        op = simplified()->ChangeUint32ToTagged();
      } else if (output_type & tInt32) {
        op = simplified()->ChangeInt32ToTagged();
      } else {
        return TypeError(node, output_type, rTagged);
      }
    } else if (output_type & rFloat64) {
      op = simplified()->ChangeFloat64ToTagged();
    } else {
      return TypeError(node, output_type, rTagged);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetFloat64RepresentationFor(Node* node, RepTypeUnion output_type) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kNumberConstant:
        return jsgraph()->Float64Constant(ValueOf<double>(node->op()));
      case IrOpcode::kInt32Constant:
        if (output_type & tUint32) {
          uint32_t value = ValueOf<uint32_t>(node->op());
          return jsgraph()->Float64Constant(static_cast<double>(value));
        } else {
          int32_t value = ValueOf<int32_t>(node->op());
          return jsgraph()->Float64Constant(value);
        }
      case IrOpcode::kFloat64Constant:
        return node;  // No change necessary.
      default:
        break;
    }
    // Select the correct X -> Float64 operator.
    Operator* op;
    if (output_type & rWord32) {
      if (output_type & tUint32) {
        op = machine()->ChangeUint32ToFloat64();
      } else if (output_type & tInt32) {
        op = machine()->ChangeInt32ToFloat64();
      } else {
        return TypeError(node, output_type, rFloat64);
      }
    } else if (output_type & rTagged) {
      op = simplified()->ChangeTaggedToFloat64();
    } else {
      return TypeError(node, output_type, rFloat64);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetWord32RepresentationFor(Node* node, RepTypeUnion output_type) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kInt32Constant:
        return node;  // No change necessary.
      case IrOpcode::kNumberConstant:
      case IrOpcode::kFloat64Constant: {
        if (output_type & tUint32) {
          int32_t value = static_cast<int32_t>(
              static_cast<uint32_t>(ValueOf<double>(node->op())));
          return jsgraph()->Int32Constant(value);
        } else if (output_type & tInt32) {
          int32_t value = FastD2I(ValueOf<double>(node->op()));
          return jsgraph()->Int32Constant(value);
        } else {
          return TypeError(node, output_type, rWord32);
        }
      }
      default:
        break;
    }
    // Select the correct X -> Word32 operator.
    Operator* op = NULL;
    if (output_type & rFloat64) {
      if (output_type & tUint32) {
        op = machine()->ChangeFloat64ToUint32();
      } else if (output_type & tInt32) {
        op = machine()->ChangeFloat64ToInt32();
      } else {
        return TypeError(node, output_type, rWord32);
      }
    } else if (output_type & rTagged) {
      if (output_type & tUint32) {
        op = simplified()->ChangeTaggedToUint32();
      } else if (output_type & tInt32) {
        op = simplified()->ChangeTaggedToInt32();
      } else {
        return TypeError(node, output_type, rWord32);
      }
    } else if (output_type & rBit) {
      return node;  // Sloppy comparison -> word32.
    } else {
      return TypeError(node, output_type, rWord32);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

  Node* GetBitRepresentationFor(Node* node, RepTypeUnion output_type) {
    // Eagerly fold representation changes for constants.
    switch (node->opcode()) {
      case IrOpcode::kInt32Constant: {
        int32_t value = ValueOf<int32_t>(node->op());
        if (value == 0 || value == 1) return node;
        return jsgraph()->OneConstant();  // value != 0
      }
      case IrOpcode::kHeapConstant: {
        Handle<Object> handle = ValueOf<Handle<Object> >(node->op());
        DCHECK(*handle == isolate()->heap()->true_value() ||
               *handle == isolate()->heap()->false_value());
        return jsgraph()->Int32Constant(
            *handle == isolate()->heap()->true_value() ? 1 : 0);
      }
      default:
        break;
    }
    // Select the correct X -> Bit operator.
    Operator* op;
    if (output_type & rWord32) {
      return node;  // No change necessary.
    } else if (output_type & rWord64) {
      return node;  // TODO(titzer): No change necessary, on 64-bit.
    } else if (output_type & rTagged) {
      op = simplified()->ChangeBoolToBit();
    } else {
      return TypeError(node, output_type, rBit);
    }
    return jsgraph()->graph()->NewNode(op, node);
  }

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

  static RepType TypeForMachineRepresentation(MachineRepresentation rep) {
    // TODO(titzer): merge MachineRepresentation and RepType.
    switch (rep) {
      case kMachineWord8:
        return rWord32;
      case kMachineWord16:
        return rWord32;
      case kMachineWord32:
        return rWord32;
      case kMachineWord64:
        return rWord64;
      case kMachineFloat64:
        return rFloat64;
      case kMachineTagged:
        return rTagged;
      default:
        UNREACHABLE();
        return static_cast<RepType>(0);
    }
  }

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

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

  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;
    }
  }

  RepType TypeForField(const FieldAccess& access) {
    RepType tElement = static_cast<RepType>(0);  // TODO(titzer)
    RepType rElement = TypeForMachineRepresentation(access.representation);
    return static_cast<RepType>(tElement | rElement);
  }

  RepType TypeForElement(const ElementAccess& access) {
    RepType tElement = static_cast<RepType>(0);  // TODO(titzer)
    RepType rElement = TypeForMachineRepresentation(access.representation);
    return static_cast<RepType>(tElement | rElement);
  }

  RepType TypeForBasePointer(Node* node) {
    Type* upper = NodeProperties::GetBounds(node).upper;
    if (upper->Is(Type::UntaggedPtr())) return rPtr;
    return static_cast<RepType>(tAny | rTagged);
  }

 private:
  JSGraph* jsgraph_;
  SimplifiedOperatorBuilder* simplified_;
  MachineOperatorBuilder* machine_;
  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, RepTypeUnion output_type, RepTypeUnion use) {
    type_error_ = true;
    if (!testing_type_errors_) {
      UNREACHABLE();  // TODO(titzer): report nicer type error
    }
    return node;
  }

  JSGraph* jsgraph() { return jsgraph_; }
  Isolate* isolate() { return isolate_; }
  SimplifiedOperatorBuilder* simplified() { return simplified_; }
  MachineOperatorBuilder* machine() { return machine_; }
};
}
}
}  // namespace v8::internal::compiler

#endif  // V8_COMPILER_REPRESENTATION_CHANGE_H_
