//===- Identifier.h - MLIR Identifier Class ---------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef MLIR_IR_IDENTIFIER_H
#define MLIR_IR_IDENTIFIER_H

#include "mlir/Support/LLVM.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/StringMapEntry.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/PointerLikeTypeTraits.h"

namespace mlir {
class Dialect;
class MLIRContext;

/// This class represents a uniqued string owned by an MLIRContext.  Strings
/// represented by this type cannot contain nul characters, and may not have a
/// zero length.
///
/// This is a POD type with pointer size, so it should be passed around by
/// value. The underlying data is owned by MLIRContext and is thus immortal for
/// almost all clients.
///
/// An Identifier may be prefixed with a dialect namespace followed by a single
/// dot `.`. This is particularly useful when used as a key in a NamedAttribute
/// to differentiate a dependent attribute (specific to an operation) from a
/// generic attribute defined by the dialect (in general applicable to multiple
/// operations).
class Identifier {
  using EntryType =
      llvm::StringMapEntry<PointerUnion<Dialect *, MLIRContext *>>;

public:
  /// Return an identifier for the specified string.
  static Identifier get(const Twine &string, MLIRContext *context);

  Identifier(const Identifier &) = default;
  Identifier &operator=(const Identifier &other) = default;

  /// Return a StringRef for the string.
  StringRef strref() const { return entry->first(); }

  /// Identifiers implicitly convert to StringRefs.
  operator StringRef() const { return strref(); }

  /// Return an std::string.
  std::string str() const { return strref().str(); }

  /// Return a null terminated C string.
  const char *c_str() const { return entry->getKeyData(); }

  /// Return a pointer to the start of the string data.
  const char *data() const { return entry->getKeyData(); }

  /// Return the number of bytes in this string.
  unsigned size() const { return entry->getKeyLength(); }

  /// Return the dialect loaded in the context for this identifier or nullptr if
  /// this identifier isn't prefixed with a loaded dialect. For example the
  /// `llvm.fastmathflags` identifier would return the LLVM dialect here,
  /// assuming it is loaded in the context.
  Dialect *getDialect();

  /// Return the current MLIRContext associated with this identifier.
  MLIRContext *getContext();

  const char *begin() const { return data(); }
  const char *end() const { return entry->getKeyData() + size(); }

  bool operator==(Identifier other) const { return entry == other.entry; }
  bool operator!=(Identifier rhs) const { return !(*this == rhs); }

  void print(raw_ostream &os) const;
  void dump() const;

  const void *getAsOpaquePointer() const {
    return static_cast<const void *>(entry);
  }
  static Identifier getFromOpaquePointer(const void *entry) {
    return Identifier(static_cast<const EntryType *>(entry));
  }

  /// Compare the underlying StringRef.
  int compare(Identifier rhs) const { return strref().compare(rhs.strref()); }

private:
  /// This contains the bytes of the string, which is guaranteed to be nul
  /// terminated.
  const EntryType *entry;
  explicit Identifier(const EntryType *entry) : entry(entry) {}
};

inline raw_ostream &operator<<(raw_ostream &os, Identifier identifier) {
  identifier.print(os);
  return os;
}

// Identifier/Identifier equality comparisons are defined inline.
inline bool operator==(Identifier lhs, StringRef rhs) {
  return lhs.strref() == rhs;
}
inline bool operator!=(Identifier lhs, StringRef rhs) { return !(lhs == rhs); }

inline bool operator==(StringRef lhs, Identifier rhs) {
  return rhs.strref() == lhs;
}
inline bool operator!=(StringRef lhs, Identifier rhs) { return !(lhs == rhs); }

// Make identifiers hashable.
inline llvm::hash_code hash_value(Identifier arg) {
  // Identifiers are uniqued, so we can just hash the pointer they contain.
  return llvm::hash_value(arg.getAsOpaquePointer());
}
} // end namespace mlir

namespace llvm {
// Identifiers hash just like pointers, there is no need to hash the bytes.
template <>
struct DenseMapInfo<mlir::Identifier> {
  static mlir::Identifier getEmptyKey() {
    auto pointer = llvm::DenseMapInfo<const void *>::getEmptyKey();
    return mlir::Identifier::getFromOpaquePointer(pointer);
  }
  static mlir::Identifier getTombstoneKey() {
    auto pointer = llvm::DenseMapInfo<const void *>::getTombstoneKey();
    return mlir::Identifier::getFromOpaquePointer(pointer);
  }
  static unsigned getHashValue(mlir::Identifier val) {
    return mlir::hash_value(val);
  }
  static bool isEqual(mlir::Identifier lhs, mlir::Identifier rhs) {
    return lhs == rhs;
  }
};

/// The pointer inside of an identifier comes from a StringMap, so its alignment
/// is always at least 4 and probably 8 (on 64-bit machines).  Allow LLVM to
/// steal the low bits.
template <>
struct PointerLikeTypeTraits<mlir::Identifier> {
public:
  static inline void *getAsVoidPointer(mlir::Identifier i) {
    return const_cast<void *>(i.getAsOpaquePointer());
  }
  static inline mlir::Identifier getFromVoidPointer(void *p) {
    return mlir::Identifier::getFromOpaquePointer(p);
  }
  static constexpr int NumLowBitsAvailable = 2;
};

} // end namespace llvm
#endif
