//===--- LocalTypeData.h - Dominance-scoped type data -----------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file defines types relating to the local caching of type data,
//  such as type metadata, value witness tables, and protocol witness tables.
//
//  Type data may be cached concretely, meaning that it was already fully
//  computed, or abstractly, meaning that we remember how to recreate it
//  but haven't actually done so yet.
//
//  Type data may be cached at different points within a function.
//  Some of these points may not dominate all possible use sites.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_IRGEN_LOCALTYPEDATA_H
#define SWIFT_IRGEN_LOCALTYPEDATA_H

#include "LocalTypeDataKind.h"
#include "DominancePoint.h"
#include "MetadataPath.h"
#include "MetadataRequest.h"
#include "swift/AST/Type.h"
#include "llvm/ADT/STLExtras.h"
#include <utility>

namespace swift {
  class TypeBase;

namespace irgen {
  class DynamicMetadataRequest;
  class MetadataResponse;
  class FulfillmentMap;
  enum IsExact_t : bool;

/// A cache of local type data.
///
/// Basic design considerations:
///
///   - We want to be able to efficiently look up a key and find something.
///     Generally this will find something from the entry block.  We shouldn't
///     have to scan all the dominating points first.
///
///   - We don't expect to have multiple definitions for a key very often.
///     Therefore, given a collision, it should be okay to scan a list and
///     ask whether each is acceptable.
class LocalTypeDataCache {
public:
  static LocalTypeDataKey getKey(CanType type, LocalTypeDataKind index) {
    return LocalTypeDataKey{ type, index };
  }

private:
  struct CacheEntry {
    enum class Kind {
      Concrete, Abstract
    };

    DominancePoint DefinitionPoint;

  private:
    enum { KindMask = 0x1, ConditionalMask = 0x2 };
    llvm::PointerIntPair<CacheEntry*, 2, unsigned> NextAndFlags;

  public:
    Kind getKind() const {
      return Kind(NextAndFlags.getInt() & KindMask);
    }
    CacheEntry *getNext() const { return NextAndFlags.getPointer(); }
    void setNext(CacheEntry *next) { NextAndFlags.setPointer(next); }

    /// Return the abstract cost of evaluating this cache entry.
    OperationCost cost() const;

    /// Return the abstract cost of evaluating this cache entry.
    OperationCost costForRequest(LocalTypeDataKey key,
                                 DynamicMetadataRequest request) const;

    /// Return whether following the metadata path immediately produces
    /// a value that's statically known to satisfy the given request, or
    /// whether a separate dynamic check is required.
    bool immediatelySatisfies(LocalTypeDataKey key,
                              DynamicMetadataRequest request) const;

    /// Destruct and deallocate this cache entry.
    void erase() const;

    bool isConditional() const {
      return NextAndFlags.getInt() & ConditionalMask;
    }

  protected:
    CacheEntry(Kind kind, DominancePoint point, bool isConditional)
        : DefinitionPoint(point),
          NextAndFlags(nullptr,
                       unsigned(kind) | (isConditional ? ConditionalMask : 0)) {
    }
    ~CacheEntry() = default;
  };

  /// A concrete entry in the cache, which directly stores the desired value.
  struct ConcreteCacheEntry : CacheEntry {
    MetadataResponse Value;

    ConcreteCacheEntry(DominancePoint point, bool isConditional,
                       MetadataResponse value)
      : CacheEntry(Kind::Concrete, point, isConditional), Value(value) {}

    OperationCost cost() const { return OperationCost::Free; }
    OperationCost costForRequest(LocalTypeDataKey key,
                                 DynamicMetadataRequest request) const;

    bool immediatelySatisfies(LocalTypeDataKey key,
                              DynamicMetadataRequest request) const;

    static bool classof(const CacheEntry *entry) {
      return entry->getKind() == Kind::Concrete;
    }
  };

  /// A source of concrete data from which abstract cache entries can be
  /// derived.
  class AbstractSource {
  public:
    enum class Kind {
      TypeMetadata,
      ProtocolWitnessTable,
    };
  private:
    CanType Type;
    void *Conformance;
    MetadataResponse Value;

  public:
    explicit AbstractSource(CanType type, ProtocolConformanceRef conformance,
                            llvm::Value *value)
      : Type(type), Conformance(conformance.getOpaqueValue()),
        Value(MetadataResponse::forComplete(value)) {
      assert(Conformance != nullptr);
    }
    explicit AbstractSource(CanType type, MetadataResponse value)
      : Type(type), Conformance(nullptr), Value(value) {}

    Kind getKind() const {
      return (Conformance ? Kind::ProtocolWitnessTable : Kind::TypeMetadata);
    }

    CanType getType() const {
      return Type;
    }
    ProtocolConformanceRef getProtocolConformance() const {
      assert(Conformance && "not a protocol conformance");
      return ProtocolConformanceRef::getFromOpaqueValue(Conformance);
    }
    MetadataResponse getValue() const {
      return Value;
    }
  };

  /// An abstract entry in the cache, which requires some amount of
  /// non-trivial evaluation to derive the desired value.
  struct AbstractCacheEntry : CacheEntry {
    unsigned SourceIndex;
    unsigned State;
    MetadataPath Path;

    AbstractCacheEntry(DominancePoint point, bool isConditional,
                       unsigned sourceIndex, MetadataPath &&path,
                       MetadataState state)
      : CacheEntry(Kind::Abstract, point, isConditional),
        SourceIndex(sourceIndex), State(unsigned(state)),
        Path(std::move(path)) {}

    MetadataResponse follow(IRGenFunction &IGF, AbstractSource &source,
                            DynamicMetadataRequest request) const;

    /// Return the natural state of the metadata that would be produced
    /// by just following the path.  If the path ends in calling a type
    /// metadata accessor, it's okay to use MetadataState::Complete here
    /// because it's just as cheap to produce that as anything else.
    /// But if the path ends in just loading type metadata from somewhere,
    /// this should be the presumed state of that type metadata so that
    /// the costing accurately includes the cost of dynamically checking
    /// the state of that metadata.
    MetadataState getState() const {
      return MetadataState(State);
    }

    OperationCost cost() const { return Path.cost(); }
    OperationCost costForRequest(LocalTypeDataKey key,
                                 DynamicMetadataRequest request) const;

    bool immediatelySatisfies(LocalTypeDataKey key,
                              DynamicMetadataRequest request) const;

    static bool classof(const CacheEntry *entry) {
      return entry->getKind() == Kind::Abstract;
    }
  };

  /// The linked list of cache entries corresponding to a particular key.
  struct CacheEntryChain {
    CacheEntry *Root;

    explicit CacheEntryChain(CacheEntry *root = nullptr) : Root(root) {}

    CacheEntryChain(const CacheEntryChain &other) = delete;
    CacheEntryChain &operator=(const CacheEntryChain &other) = delete;

    CacheEntryChain(CacheEntryChain &&other) : Root(other.Root) {
      other.Root = nullptr;
    }
    CacheEntryChain &operator=(CacheEntryChain &&other) {
      Root = other.Root;
      other.Root = nullptr;
      return *this;
    }

    void push_front(CacheEntry *entry) {
      entry->setNext(Root);
      Root = entry;
    }

    void eraseEntry(CacheEntry *prev, CacheEntry *entry) {
      if (prev) {
        assert(prev->getNext() == entry);
        prev->setNext(entry->getNext());
      } else {
        assert(Root == entry);
        Root = entry->getNext();
      }
      entry->erase();
    }

    /// Delete the linked list.
    ~CacheEntryChain() {
      auto next = Root;
      while (next) {
        auto cur = next;
        next = cur->getNext();
        cur->erase();
      }
    }
  };

  llvm::DenseMap<LocalTypeDataKey, CacheEntryChain> Map;

  std::vector<AbstractSource> AbstractSources;

  void addAbstractForFulfillments(IRGenFunction &IGF,
                                  FulfillmentMap &&fulfillments,
                            llvm::function_ref<AbstractSource()> createSource);


public:
  LocalTypeDataCache() = default;
  LocalTypeDataCache(const LocalTypeDataCache &other) = delete;
  LocalTypeDataCache &operator=(const LocalTypeDataCache &other) = delete;

  /// Load the value from cache if possible.  This may require emitting
  /// code if the value is cached abstractly.
  MetadataResponse tryGet(IRGenFunction &IGF, LocalTypeDataKey key,
                          bool allowAbstract, DynamicMetadataRequest request);

  /// Whether the cached state was advanced or otherwise why not.
  enum class StateAdvancement {
    /// No entry whose state could be advanced was found.
    NoEntry,
    /// An entry was found whose state was already advanced at least as far as
    /// the indicated state.
    AlreadyAtLeast,
    /// The state was advanced.
    Advanced,
  };

  /// Advances the state cached for \p key to \p state within the active
  /// dominance scope.
  StateAdvancement advanceStateInScope(IRGenFunction &IGF, LocalTypeDataKey key,
                                       MetadataState state);

  /// Add a new concrete entry to the cache at the given definition point.
  void addConcrete(DominancePoint point, bool isConditional,
                   LocalTypeDataKey key, MetadataResponse value) {
    key = key.getCachingKey();

    assert((key.Kind.isAnyTypeMetadata() ||
            value.isStaticallyKnownComplete()) &&
           "only type metadata can be added in a non-complete state");
    auto newEntry = new ConcreteCacheEntry(point, isConditional, value);
    Map[key].push_front(newEntry);
  }

  /// Add entries based on what can be fulfilled from the given type metadata.
  void addAbstractForTypeMetadata(IRGenFunction &IGF, CanType type,
                                  IsExact_t isExact, MetadataResponse value);

  void dump() const;

  // Private details for ConditionalDominanceScope.
  void eraseConditional(ArrayRef<LocalTypeDataKey> keys);
};

}
}

#endif
