// 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.

#include "location-aarch32.h"

#include "assembler-aarch32.h"
#include "macro-assembler-aarch32.h"

namespace vixl {

namespace aarch32 {

bool Location::Needs16BitPadding(int32_t location) const {
  if (!HasForwardReferences()) return false;
  const ForwardRef& last_ref = GetLastForwardReference();
  int32_t min_location_last_ref = last_ref.GetMinLocation();
  VIXL_ASSERT(min_location_last_ref - location <= 2);
  return (min_location_last_ref > location);
}

void Location::ResolveReferences(internal::AssemblerBase* assembler) {
  // Iterate over references and call EncodeLocationFor on each of them.
  for (ForwardRefListIterator it(this); !it.Done(); it.Advance()) {
    const ForwardRef& reference = *it.Current();
    VIXL_ASSERT(reference.LocationIsEncodable(location_));
    int32_t from = reference.GetLocation();
    EncodeLocationFor(assembler, from, reference.op());
  }
  forward_.clear();
}

static bool Is16BitEncoding(uint16_t instr) {
  return instr < (kLowestT32_32Opcode >> 16);
}

void Location::EncodeLocationFor(internal::AssemblerBase* assembler,
                                 int32_t from,
                                 const Location::EmitOperator* encoder) {
  if (encoder->IsUsingT32()) {
    uint16_t* instr_ptr =
        assembler->GetBuffer()->GetOffsetAddress<uint16_t*>(from);
    if (Is16BitEncoding(instr_ptr[0])) {
      // The Encode methods always deals with uint32_t types so we need
      // to explicitly cast it.
      uint32_t instr = static_cast<uint32_t>(instr_ptr[0]);
      instr = encoder->Encode(instr, from, this);
      // The Encode method should not ever set the top 16 bits.
      VIXL_ASSERT((instr & ~0xffff) == 0);
      instr_ptr[0] = static_cast<uint16_t>(instr);
    } else {
      uint32_t instr =
          instr_ptr[1] | (static_cast<uint32_t>(instr_ptr[0]) << 16);
      instr = encoder->Encode(instr, from, this);
      instr_ptr[0] = static_cast<uint16_t>(instr >> 16);
      instr_ptr[1] = static_cast<uint16_t>(instr);
    }
  } else {
    uint32_t* instr_ptr =
        assembler->GetBuffer()->GetOffsetAddress<uint32_t*>(from);
    instr_ptr[0] = encoder->Encode(instr_ptr[0], from, this);
  }
}

void Location::AddForwardRef(int32_t instr_location,
                             const EmitOperator& op,
                             const ReferenceInfo* info) {
  VIXL_ASSERT(referenced_);
  int32_t from = instr_location + (op.IsUsingT32() ? kT32PcDelta : kA32PcDelta);
  if (info->pc_needs_aligning == ReferenceInfo::kAlignPc)
    from = AlignDown(from, 4);
  int32_t min_object_location = from + info->min_offset;
  int32_t max_object_location = from + info->max_offset;
  forward_.insert(ForwardRef(&op,
                             instr_location,
                             info->size,
                             min_object_location,
                             max_object_location,
                             info->alignment));
}

int Location::GetMaxAlignment() const {
  int max_alignment = GetPoolObjectAlignment();
  for (ForwardRefListIterator it(const_cast<Location*>(this)); !it.Done();
       it.Advance()) {
    const ForwardRef& reference = *it.Current();
    if (reference.GetAlignment() > max_alignment)
      max_alignment = reference.GetAlignment();
  }
  return max_alignment;
}

int Location::GetMinLocation() const {
  int32_t min_location = 0;
  for (ForwardRefListIterator it(const_cast<Location*>(this)); !it.Done();
       it.Advance()) {
    const ForwardRef& reference = *it.Current();
    if (reference.GetMinLocation() > min_location)
      min_location = reference.GetMinLocation();
  }
  return min_location;
}

void Label::UpdatePoolObject(PoolObject<int32_t>* object) {
  VIXL_ASSERT(forward_.size() == 1);
  const ForwardRef& reference = forward_.Front();
  object->Update(reference.GetMinLocation(),
                 reference.GetMaxLocation(),
                 reference.GetAlignment());
}

void Label::EmitPoolObject(MacroAssemblerInterface* masm) {
  MacroAssembler* macro_assembler = static_cast<MacroAssembler*>(masm);

  // Add a new branch to this label.
  VIXL_ASSERT(macro_assembler->GetBuffer()->HasSpaceFor(kMaxInstructionSizeInBytes));
  ExactAssemblyScopeWithoutPoolsCheck guard(macro_assembler,
                                            kMaxInstructionSizeInBytes,
                                            ExactAssemblyScope::kMaximumSize);
  macro_assembler->b(this);
}

void RawLiteral::EmitPoolObject(MacroAssemblerInterface* masm) {
  Assembler* assembler = static_cast<Assembler*>(masm->AsAssemblerBase());

  VIXL_ASSERT(assembler->GetBuffer()->HasSpaceFor(GetSize()));
  assembler->GetBuffer()->EmitData(GetDataAddress(), GetSize());
}
}
}
