// Copyright 2017, VIXL authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of ARM Limited nor the names of its contributors may be
//     used to endorse or promote products derived from this software without
//     specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef VIXL_AARCH32_LABEL_AARCH32_H_
#define VIXL_AARCH32_LABEL_AARCH32_H_

extern "C" {
#include <stdint.h>
}

#include <algorithm>
#include <cstddef>
#include <iomanip>
#include <list>

#include "../invalset-vixl.h"
#include "../pool-manager.h"
#include "../utils-vixl.h"

#include "constants-aarch32.h"
#include "instructions-aarch32.h"

namespace vixl {

namespace aarch32 {

class MacroAssembler;

class Location : public LocationBase<int32_t> {
  friend class Assembler;
  friend class MacroAssembler;

 public:
  // Unbound location that can be used with the assembler bind() method and
  // with the assembler methods for generating instructions, but will never
  // be handled by the pool manager.
  Location()
      : LocationBase<int32_t>(kRawLocation, 1 /* placeholder size*/),
        referenced_(false) {}

  typedef int32_t Offset;

  ~Location() VIXL_NEGATIVE_TESTING_ALLOW_EXCEPTION {
#ifdef VIXL_DEBUG
    if (IsReferenced() && !IsBound()) {
      VIXL_ABORT_WITH_MSG("Location, label or literal used but not bound.\n");
    }
#endif
  }

  bool IsReferenced() const { return referenced_; }

 private:
  class EmitOperator {
   public:
    explicit EmitOperator(InstructionSet isa) : isa_(isa) {
#if defined(VIXL_INCLUDE_TARGET_A32_ONLY)
      USE(isa_);
      VIXL_ASSERT(isa == A32);
#elif defined(VIXL_INCLUDE_TARGET_T32_ONLY)
      USE(isa_);
      VIXL_ASSERT(isa == T32);
#endif
    }
    virtual ~EmitOperator() {}
    virtual uint32_t Encode(uint32_t /*instr*/,
                            Location::Offset /*pc*/,
                            const Location* /*label*/) const {
      return 0;
    }
#if defined(VIXL_INCLUDE_TARGET_A32_ONLY)
    bool IsUsingT32() const { return false; }
#elif defined(VIXL_INCLUDE_TARGET_T32_ONLY)
    bool IsUsingT32() const { return true; }
#else
    bool IsUsingT32() const { return isa_ == T32; }
#endif

   private:
    InstructionSet isa_;
  };

 protected:
  class ForwardRef : public ForwardReference<int32_t> {
   public:
    // Default constructor for InvalSet.
    ForwardRef() : ForwardReference<int32_t>(0, 0, 0, 0, 1), op_(NULL) {}

    ForwardRef(const Location::EmitOperator* op,
               int32_t location,
               int size,
               int32_t min_object_location,
               int32_t max_object_location,
               int object_alignment = 1)
        : ForwardReference<int32_t>(location,
                                    size,
                                    min_object_location,
                                    max_object_location,
                                    object_alignment),
          op_(op) {}

    const Location::EmitOperator* op() const { return op_; }

    // We must provide comparison operators to work with InvalSet.
    bool operator==(const ForwardRef& other) const {
      return GetLocation() == other.GetLocation();
    }
    bool operator<(const ForwardRef& other) const {
      return GetLocation() < other.GetLocation();
    }
    bool operator<=(const ForwardRef& other) const {
      return GetLocation() <= other.GetLocation();
    }
    bool operator>(const ForwardRef& other) const {
      return GetLocation() > other.GetLocation();
    }

   private:
    const Location::EmitOperator* op_;
  };

  static const int kNPreallocatedElements = 4;
  // The following parameters will not affect ForwardRefList in practice, as we
  // resolve all references at once and clear the list, so we do not need to
  // remove individual elements by invalidating them.
  static const int32_t kInvalidLinkKey = INT32_MAX;
  static const size_t kReclaimFrom = 512;
  static const size_t kReclaimFactor = 2;

  typedef InvalSet<ForwardRef,
                   kNPreallocatedElements,
                   int32_t,
                   kInvalidLinkKey,
                   kReclaimFrom,
                   kReclaimFactor>
      ForwardRefListBase;
  typedef InvalSetIterator<ForwardRefListBase> ForwardRefListIteratorBase;

  class ForwardRefList : public ForwardRefListBase {
   public:
    ForwardRefList() : ForwardRefListBase() {}

    using ForwardRefListBase::Back;
    using ForwardRefListBase::Front;
  };

  class ForwardRefListIterator : public ForwardRefListIteratorBase {
   public:
    explicit ForwardRefListIterator(Location* location)
        : ForwardRefListIteratorBase(&location->forward_) {}

    // TODO: Remove these and use the STL-like interface instead. We'll need a
    // const_iterator implemented for this.
    using ForwardRefListIteratorBase::Advance;
    using ForwardRefListIteratorBase::Current;
  };

  // For InvalSet::GetKey() and InvalSet::SetKey().
  friend class InvalSet<ForwardRef,
                        kNPreallocatedElements,
                        int32_t,
                        kInvalidLinkKey,
                        kReclaimFrom,
                        kReclaimFactor>;

 private:
  virtual void ResolveReferences(internal::AssemblerBase* assembler)
      VIXL_OVERRIDE;

  void SetReferenced() { referenced_ = true; }

  bool HasForwardReferences() const { return !forward_.empty(); }

  ForwardRef GetLastForwardReference() const {
    VIXL_ASSERT(HasForwardReferences());
    return forward_.Back();
  }

  // Add forward reference to this object. Called from the assembler.
  void AddForwardRef(int32_t instr_location,
                     const EmitOperator& op,
                     const ReferenceInfo* info);

  // Check if we need to add padding when binding this object, in order to
  // meet the minimum location requirement.
  bool Needs16BitPadding(int location) const;

  void EncodeLocationFor(internal::AssemblerBase* assembler,
                         int32_t from,
                         const Location::EmitOperator* encoder);

  // True if the label has been used at least once.
  bool referenced_;

 protected:
  // Types passed to LocationBase. Must be distinct for unbound Locations (not
  // relevant for bound locations, as they don't have a corresponding
  // PoolObject).
  static const int kRawLocation = 0;  // Will not be used by the pool manager.
  static const int kVeneerType = 1;
  static const int kLiteralType = 2;

  // Contains the references to the unbound label
  ForwardRefList forward_;

  // To be used only by derived classes.
  Location(uint32_t type, int size, int alignment)
      : LocationBase<int32_t>(type, size, alignment), referenced_(false) {}

  // To be used only by derived classes.
  explicit Location(Offset location)
      : LocationBase<int32_t>(location), referenced_(false) {}

  virtual int GetMaxAlignment() const VIXL_OVERRIDE;
  virtual int GetMinLocation() const VIXL_OVERRIDE;

 private:
  // Included to make the class concrete, however should never be called.
  virtual void EmitPoolObject(MacroAssemblerInterface* masm) VIXL_OVERRIDE {
    USE(masm);
    VIXL_UNREACHABLE();
  }
};

class Label : public Location {
  static const int kVeneerSize = 4;
  // Use an alignment of 1 for all architectures. Even though we can bind an
  // unused label, because of the way the MacroAssembler works we can always be
  // sure to have the correct buffer alignment for the instruction set we are
  // using, so we do not need to enforce additional alignment requirements
  // here.
  // TODO: Consider modifying the interface of the pool manager to pass an
  // optional additional alignment to Bind() in order to handle cases where the
  // buffer could be unaligned.
  static const int kVeneerAlignment = 1;

 public:
  Label() : Location(kVeneerType, kVeneerSize, kVeneerAlignment) {}
  explicit Label(Offset location) : Location(location) {}

 private:
  virtual bool ShouldBeDeletedOnPlacementByPoolManager() const VIXL_OVERRIDE {
    return false;
  }
  virtual bool ShouldDeletePoolObjectOnPlacement() const VIXL_OVERRIDE {
    return false;
  }

  virtual void UpdatePoolObject(PoolObject<int32_t>* object) VIXL_OVERRIDE;
  virtual void EmitPoolObject(MacroAssemblerInterface* masm) VIXL_OVERRIDE;

  virtual bool UsePoolObjectEmissionMargin() const VIXL_OVERRIDE {
    return true;
  }
  virtual int32_t GetPoolObjectEmissionMargin() const VIXL_OVERRIDE {
    VIXL_ASSERT(UsePoolObjectEmissionMargin() == true);
    return 1 * KBytes;
  }
};

class RawLiteral : public Location {
  // Some load instructions require alignment to 4 bytes. Since we do
  // not know what instructions will reference a literal after we place
  // it, we enforce a 4 byte alignment for literals that are 4 bytes or
  // larger.
  static const int kLiteralAlignment = 4;

 public:
  enum PlacementPolicy { kPlacedWhenUsed, kManuallyPlaced };

  enum DeletionPolicy {
    kDeletedOnPlacementByPool,
    kDeletedOnPoolDestruction,
    kManuallyDeleted
  };

  RawLiteral(const void* addr,
             int size,
             PlacementPolicy placement_policy = kPlacedWhenUsed,
             DeletionPolicy deletion_policy = kManuallyDeleted)
      : Location(kLiteralType,
                 size,
                 (size < kLiteralAlignment) ? size : kLiteralAlignment),
        addr_(addr),
        manually_placed_(placement_policy == kManuallyPlaced),
        deletion_policy_(deletion_policy) {
    // We can't have manually placed literals that are not manually deleted.
    VIXL_ASSERT(!IsManuallyPlaced() ||
                (GetDeletionPolicy() == kManuallyDeleted));
  }
  RawLiteral(const void* addr, int size, DeletionPolicy deletion_policy)
      : Location(kLiteralType,
                 size,
                 (size < kLiteralAlignment) ? size : kLiteralAlignment),
        addr_(addr),
        manually_placed_(false),
        deletion_policy_(deletion_policy) {}
  const void* GetDataAddress() const { return addr_; }
  int GetSize() const { return GetPoolObjectSizeInBytes(); }

  bool IsManuallyPlaced() const { return manually_placed_; }

 private:
  DeletionPolicy GetDeletionPolicy() const { return deletion_policy_; }

  virtual bool ShouldBeDeletedOnPlacementByPoolManager() const VIXL_OVERRIDE {
    return GetDeletionPolicy() == kDeletedOnPlacementByPool;
  }
  virtual bool ShouldBeDeletedOnPoolManagerDestruction() const VIXL_OVERRIDE {
    return GetDeletionPolicy() == kDeletedOnPoolDestruction;
  }
  virtual void EmitPoolObject(MacroAssemblerInterface* masm) VIXL_OVERRIDE;

  // Data address before it's moved into the code buffer.
  const void* const addr_;
  // When this flag is true, the label will be placed manually.
  bool manually_placed_;
  // When is the literal to be removed from the memory
  // Can be delete'd when:
  //   moved into the code buffer: kDeletedOnPlacementByPool
  //   the pool is delete'd: kDeletedOnPoolDestruction
  //   or left to the application: kManuallyDeleted.
  DeletionPolicy deletion_policy_;

  friend class MacroAssembler;
};

template <typename T>
class Literal : public RawLiteral {
 public:
  explicit Literal(const T& value,
                   PlacementPolicy placement_policy = kPlacedWhenUsed,
                   DeletionPolicy deletion_policy = kManuallyDeleted)
      : RawLiteral(&value_, sizeof(T), placement_policy, deletion_policy),
        value_(value) {}
  explicit Literal(const T& value, DeletionPolicy deletion_policy)
      : RawLiteral(&value_, sizeof(T), deletion_policy), value_(value) {}
  void UpdateValue(const T& value, CodeBuffer* buffer) {
    value_ = value;
    if (IsBound()) {
      buffer->UpdateData(GetLocation(), GetDataAddress(), GetSize());
    }
  }

 private:
  T value_;
};

class StringLiteral : public RawLiteral {
 public:
  explicit StringLiteral(const char* str,
                         PlacementPolicy placement_policy = kPlacedWhenUsed,
                         DeletionPolicy deletion_policy = kManuallyDeleted)
      : RawLiteral(str,
                   static_cast<int>(strlen(str) + 1),
                   placement_policy,
                   deletion_policy) {
    VIXL_ASSERT((strlen(str) + 1) <= kMaxObjectSize);
  }
  explicit StringLiteral(const char* str, DeletionPolicy deletion_policy)
      : RawLiteral(str, static_cast<int>(strlen(str) + 1), deletion_policy) {
    VIXL_ASSERT((strlen(str) + 1) <= kMaxObjectSize);
  }
};

}  // namespace aarch32


// Required InvalSet template specialisations.
#define INVAL_SET_TEMPLATE_PARAMETERS                                       \
  aarch32::Location::ForwardRef, aarch32::Location::kNPreallocatedElements, \
      int32_t, aarch32::Location::kInvalidLinkKey,                          \
      aarch32::Location::kReclaimFrom, aarch32::Location::kReclaimFactor
template <>
inline int32_t InvalSet<INVAL_SET_TEMPLATE_PARAMETERS>::GetKey(
    const aarch32::Location::ForwardRef& element) {
  return element.GetLocation();
}
template <>
inline void InvalSet<INVAL_SET_TEMPLATE_PARAMETERS>::SetKey(
    aarch32::Location::ForwardRef* element, int32_t key) {
  element->SetLocationToInvalidateOnly(key);
}
#undef INVAL_SET_TEMPLATE_PARAMETERS

}  // namespace vixl

#endif  // VIXL_AARCH32_LABEL_AARCH32_H_
