//===--- Cache.h - Caching mechanism interface -------------------*- C++ -*-==//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_BASIC_CACHE_H
#define SWIFT_BASIC_CACHE_H

#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Optional.h"

namespace swift {
namespace sys {

template <typename T>
struct CacheTypeMgmtInfo {
  static void *enterCache(const T &Val) { return new T(Val); }
  static void exitCache(void *Ptr) { delete static_cast<T*>(Ptr); }
  static const T &getFromCache(void *Ptr) { return *static_cast<T*>(Ptr); }
};

template <typename T>
struct CacheKeyHashInfo {
  static uintptr_t getHashValue(const T &Val) {
    return llvm::DenseMapInfo<T>::getHashValue(Val);
  }
  static bool isEqual(void *LHS, void *RHS) {
    return llvm::DenseMapInfo<T>::isEqual(*static_cast<T*>(LHS),
                                          *static_cast<T*>(RHS));
  }
};

template <typename T>
struct CacheValueCostInfo {
  static size_t getCost(const T &Val) { return sizeof(Val); }
};

template <typename T>
struct CacheKeyInfo : public CacheKeyHashInfo<T>,
                      public CacheTypeMgmtInfo<T> {
  static const void *getLookupKey(const T *Val) { return Val; }
};

template <typename T>
struct CacheValueInfo : public CacheValueCostInfo<T>,
                        public CacheTypeMgmtInfo<T> {
};

/// The underlying implementation of the caching mechanism.
/// It should be inherently thread-safe.
class CacheImpl {
public:
  typedef void *ImplTy;

  struct CallBacks {
    void *UserData;

    uintptr_t (*keyHashCB)(void *Key, void *UserData);
    bool (*keyIsEqualCB)(void *Key1, void *Key2, void *UserData);

    void (*keyDestroyCB)(void *Key, void *UserData);
    void (*valueDestroyCB)(void *Value, void *UserData);
  };

protected:
  CacheImpl() = default;

  ImplTy Impl = nullptr;

  static ImplTy create(llvm::StringRef Name, const CallBacks &CBs);

  /// Sets value for key.
  ///
  /// \param Key Key to add.  Must not be nullptr.
  /// \param Value Value to add. If value is nullptr, key is associated with the
  /// value nullptr.
  /// \param Cost Cost of maintaining value in cache.
  ///
  /// Sets value for key.  Value is retained until released using
  /// \c releaseValue().
  ///
  /// Replaces previous key and value if present.  Invokes the key destroy
  /// callback immediately for the previous key.  Invokes the value destroy
  /// callback once the previous value's retain count is zero.
  ///
  /// Cost indicates the relative cost of maintaining value in the cache
  /// (e.g., size of value in bytes) and may be used by the cache under
  /// memory pressure to select which cache values to evict.  Zero is a
  /// valid cost.
  void setAndRetain(void *Key, void *Value, size_t Cost);

  /// Fetches value for key.
  ///
  /// \param Key Key used to lookup value.  Must not be nullptr.
  /// \param Value_out Value is stored here if found.  Must not be nullptr.
  /// \returns True if the key was found, false otherwise.
  ///
  /// Fetches value for key, retains value, and stores value in value_out.
  /// Caller should release value using \c releaseValue().
  bool getAndRetain(const void *Key, void **Value_out);

  /// Releases a previously retained cache value.
  ///
  /// \param Value Value to release.  Must not be nullptr.
  ///
  /// Releases a previously retained cache value. When the reference count
  /// reaches zero the cache may destroy the value.
  void releaseValue(void *Value);

  /// Removes a key and its value.
  ///
  /// \param Key Key to remove.  Must not be nullptr.
  /// \returns True if the key was found, false otherwise.
  ///
  /// Removes a key and its value from the cache such that \c getAndRetain()
  /// will return false.  Invokes the key destroy callback immediately.
  /// Invokes the value destroy callback once value's retain count is zero.
  bool remove(const void *Key);

  /// Invokes \c remove on all keys.
  void removeAll();

  /// Destroys cache.
  void destroy();
};

/// Caching mechanism, that is thread-safe and can evict its entries when there
/// is memory pressure.
///
/// This works like a dictionary, you use a key to store and retrieve a value.
/// The value is copied (during storing or retrieval), but an IntrusiveRefCntPtr
/// can be used directly as a value.
///
/// It is important to provide a proper 'cost' function for the value (via
/// \c CacheValueCostInfo trait); e.g. the cost for an ASTContext would be the
/// memory usage of the data structures it owns.
template <typename KeyT, typename ValueT,
          typename KeyInfoT = CacheKeyInfo<KeyT>,
          typename ValueInfoT = CacheValueInfo<ValueT> >
class Cache : CacheImpl {
public:
  explicit Cache(llvm::StringRef Name) {
    CallBacks CBs = {
      /*UserData=*/nullptr,
      keyHash,
      keyIsEqual,
      keyDestroy,
      valueDestroy
    };
    Impl = create(Name, CBs);
  }

  ~Cache() {
    destroy();
  }

  void set(const KeyT &Key, const ValueT &Value) {
    void *CacheKeyPtr = KeyInfoT::enterCache(Key);
    void *CacheValuePtr = ValueInfoT::enterCache(Value);
    setAndRetain(CacheKeyPtr, CacheValuePtr, ValueInfoT::getCost(Value));
    releaseValue(CacheValuePtr);
  }

  llvm::Optional<ValueT> get(const KeyT &Key) {
    const void *CacheKeyPtr = KeyInfoT::getLookupKey(&Key);
    void *CacheValuePtr;
    bool Found = getAndRetain(CacheKeyPtr, &CacheValuePtr);
    if (!Found)
      return llvm::None;

    ValueT Val(ValueInfoT::getFromCache(CacheValuePtr));
    releaseValue(CacheValuePtr);
    return std::move(Val);
  }

  /// \returns True if the key was found, false otherwise.
  bool remove(const KeyT &Key) {
    const void *CacheKeyPtr = KeyInfoT::getLookupKey(&Key);
    return CacheImpl::remove(CacheKeyPtr);
  }

  void clear() {
    removeAll();
  }

private:
  static uintptr_t keyHash(void *Key, void *UserData) {
    return KeyInfoT::getHashValue(*static_cast<KeyT*>(Key));
  }
  static bool keyIsEqual(void *Key1, void *Key2, void *UserData) {
    return KeyInfoT::isEqual(Key1, Key2);
  }

  static void keyDestroy(void *Key, void *UserData) {
    KeyInfoT::exitCache(Key);
  }
  static void valueDestroy(void *Value, void *UserData) {
    ValueInfoT::exitCache(Value);
  }
};

template <typename T>
struct CacheValueInfo<llvm::IntrusiveRefCntPtr<T>>{
  static void *enterCache(const llvm::IntrusiveRefCntPtr<T> &Val) {
    T *Ptr = Val.get();
    Ptr->Retain();
    return Ptr;
  }
  static void exitCache(void *Ptr) {
    static_cast<T*>(Ptr)->Release();
  }
  static llvm::IntrusiveRefCntPtr<T> getFromCache(void *Ptr) {
    return static_cast<T*>(Ptr);
  }
  static size_t getCost(const llvm::IntrusiveRefCntPtr<T> &Val) {
    return CacheValueCostInfo<T>::getCost(*Val);
  }
};

} // namespace sys
} // namespace swift

#endif
