/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_COMPILER_OPTIMIZING_DATA_TYPE_H_
#define ART_COMPILER_OPTIMIZING_DATA_TYPE_H_

#include <iosfwd>

#include <android-base/logging.h>

#include "base/bit_utils.h"

namespace art {

class DataType {
 public:
  enum class Type : uint8_t {
    kReference = 0,
    kBool,
    kUint8,
    kInt8,
    kUint16,
    kInt16,
    kUint32,
    kInt32,
    kUint64,
    kInt64,
    kFloat32,
    kFloat64,
    kVoid,
    kLast = kVoid
  };

  static constexpr Type FromShorty(char type);
  static constexpr char TypeId(DataType::Type type);

  static constexpr size_t SizeShift(Type type) {
    switch (type) {
      case Type::kVoid:
      case Type::kBool:
      case Type::kUint8:
      case Type::kInt8:
        return 0;
      case Type::kUint16:
      case Type::kInt16:
        return 1;
      case Type::kUint32:
      case Type::kInt32:
      case Type::kFloat32:
        return 2;
      case Type::kUint64:
      case Type::kInt64:
      case Type::kFloat64:
        return 3;
      case Type::kReference:
        return WhichPowerOf2(kObjectReferenceSize);
      default:
        LOG(FATAL) << "Invalid type " << static_cast<int>(type);
        return 0;
    }
  }

  static constexpr size_t Size(Type type) {
    switch (type) {
      case Type::kVoid:
        return 0;
      case Type::kBool:
      case Type::kUint8:
      case Type::kInt8:
        return 1;
      case Type::kUint16:
      case Type::kInt16:
        return 2;
      case Type::kUint32:
      case Type::kInt32:
      case Type::kFloat32:
        return 4;
      case Type::kUint64:
      case Type::kInt64:
      case Type::kFloat64:
        return 8;
      case Type::kReference:
        return kObjectReferenceSize;
      default:
        LOG(FATAL) << "Invalid type " << static_cast<int>(type);
        return 0;
    }
  }

  static bool IsFloatingPointType(Type type) {
    return type == Type::kFloat32 || type == Type::kFloat64;
  }

  static bool IsIntegralType(Type type) {
    // The Java language does not allow treating boolean as an integral type but
    // our bit representation makes it safe.
    switch (type) {
      case Type::kBool:
      case Type::kUint8:
      case Type::kInt8:
      case Type::kUint16:
      case Type::kInt16:
      case Type::kUint32:
      case Type::kInt32:
      case Type::kUint64:
      case Type::kInt64:
        return true;
      default:
        return false;
    }
  }

  static bool IsIntOrLongType(Type type) {
    return type == Type::kInt32 || type == Type::kInt64;
  }

  static bool Is64BitType(Type type) {
    return type == Type::kUint64 || type == Type::kInt64 || type == Type::kFloat64;
  }

  static bool IsUnsignedType(Type type) {
    return type == Type::kBool || type == Type::kUint8 || type == Type::kUint16 ||
        type == Type::kUint32 || type == Type::kUint64;
  }

  // Return the general kind of `type`, fusing integer-like types as Type::kInt.
  static Type Kind(Type type) {
    switch (type) {
      case Type::kBool:
      case Type::kUint8:
      case Type::kInt8:
      case Type::kUint16:
      case Type::kInt16:
      case Type::kUint32:
      case Type::kInt32:
        return Type::kInt32;
      case Type::kUint64:
      case Type::kInt64:
        return Type::kInt64;
      default:
        return type;
    }
  }

  static int64_t MinValueOfIntegralType(Type type) {
    switch (type) {
      case Type::kBool:
        return std::numeric_limits<bool>::min();
      case Type::kUint8:
        return std::numeric_limits<uint8_t>::min();
      case Type::kInt8:
        return std::numeric_limits<int8_t>::min();
      case Type::kUint16:
        return std::numeric_limits<uint16_t>::min();
      case Type::kInt16:
        return std::numeric_limits<int16_t>::min();
      case Type::kUint32:
        return std::numeric_limits<uint32_t>::min();
      case Type::kInt32:
        return std::numeric_limits<int32_t>::min();
      case Type::kUint64:
        return std::numeric_limits<uint64_t>::min();
      case Type::kInt64:
        return std::numeric_limits<int64_t>::min();
      default:
        LOG(FATAL) << "non integral type";
    }
    return 0;
  }

  static int64_t MaxValueOfIntegralType(Type type) {
    switch (type) {
      case Type::kBool:
        return std::numeric_limits<bool>::max();
      case Type::kUint8:
        return std::numeric_limits<uint8_t>::max();
      case Type::kInt8:
        return std::numeric_limits<int8_t>::max();
      case Type::kUint16:
        return std::numeric_limits<uint16_t>::max();
      case Type::kInt16:
        return std::numeric_limits<int16_t>::max();
      case Type::kUint32:
        return std::numeric_limits<uint32_t>::max();
      case Type::kInt32:
        return std::numeric_limits<int32_t>::max();
      case Type::kUint64:
        return std::numeric_limits<uint64_t>::max();
      case Type::kInt64:
        return std::numeric_limits<int64_t>::max();
      default:
        LOG(FATAL) << "non integral type";
    }
    return 0;
  }

  static bool IsTypeConversionImplicit(Type input_type, Type result_type);
  static bool IsTypeConversionImplicit(int64_t value, Type result_type);

  static bool IsZeroExtension(Type input_type, Type result_type) {
    return IsIntOrLongType(result_type) &&
        IsUnsignedType(input_type) &&
        Size(result_type) > Size(input_type);
  }

  static Type ToSigned(Type type) {
    switch (type) {
      case Type::kUint8:
        return Type::kInt8;
      case Type::kUint16:
        return Type::kInt16;
      case Type::kUint32:
        return Type::kInt32;
      case Type::kUint64:
        return Type::kInt64;
      default:
        return type;
    }
  }

  static Type ToUnsigned(Type type) {
    switch (type) {
      case Type::kInt8:
        return Type::kUint8;
      case Type::kInt16:
        return Type::kUint16;
      case Type::kInt32:
        return Type::kUint32;
      case Type::kInt64:
        return Type::kUint64;
      default:
        return type;
    }
  }

  static const char* PrettyDescriptor(Type type);

 private:
  static constexpr size_t kObjectReferenceSize = 4u;
};
std::ostream& operator<<(std::ostream& os, DataType::Type data_type);

// Defined outside DataType to have the operator<< available for DCHECK_NE().
inline bool DataType::IsTypeConversionImplicit(Type input_type, Type result_type) {
  DCHECK_NE(DataType::Type::kVoid, result_type);
  DCHECK_NE(DataType::Type::kVoid, input_type);

  // Invariant: We should never generate a conversion to a Boolean value.
  DCHECK_NE(DataType::Type::kBool, result_type);

  // Besides conversion to the same type, integral conversions to non-Int64 types
  // are implicit if the result value range covers the input value range, i.e.
  // widening conversions that do not need to trim the sign bits.
  return result_type == input_type ||
         (result_type != Type::kInt64 &&
          IsIntegralType(input_type) &&
          IsIntegralType(result_type) &&
          MinValueOfIntegralType(input_type) >= MinValueOfIntegralType(result_type) &&
          MaxValueOfIntegralType(input_type) <= MaxValueOfIntegralType(result_type));
}

inline bool DataType::IsTypeConversionImplicit(int64_t value, Type result_type) {
  if (IsIntegralType(result_type) && result_type != Type::kInt64) {
    // If the constant value falls in the range of the result_type, type
    // conversion isn't needed.
    return value >= MinValueOfIntegralType(result_type) &&
           value <= MaxValueOfIntegralType(result_type);
  }
  // Conversion isn't implicit if it's into non-integer types, or 64-bit int
  // which may have different number of registers.
  return false;
}

}  // namespace art

#endif  // ART_COMPILER_OPTIMIZING_DATA_TYPE_H_
