//===- subzero/src/IceAssemblerARM32.h - Assembler for ARM32 ----*- C++ -*-===//
//
// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Modified by the Subzero authors.
//
//===----------------------------------------------------------------------===//
//
//                        The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Declares the Assembler class for ARM32.
///
/// Note: All references to ARM "section" documentation refers to the "ARM
/// Architecture Reference Manual, ARMv7-A and ARMv7-R edition". See:
/// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0406c
///
//===----------------------------------------------------------------------===//

#ifndef SUBZERO_SRC_ICEASSEMBLERARM32_H
#define SUBZERO_SRC_ICEASSEMBLERARM32_H

#include "IceAssembler.h"
#include "IceConditionCodesARM32.h"
#include "IceDefs.h"
#include "IceFixups.h"
#include "IceInstARM32.h"
#include "IceRegistersARM32.h"
#include "IceTargetLowering.h"

namespace Ice {
namespace ARM32 {

/// Handles encoding of bottom/top 16 bits of an address using movw/movt.
class MoveRelocatableFixup final : public AssemblerFixup {
  MoveRelocatableFixup &operator=(const MoveRelocatableFixup &) = delete;
  MoveRelocatableFixup(const MoveRelocatableFixup &) = default;

public:
  MoveRelocatableFixup() = default;
  size_t emit(GlobalContext *Ctx, const Assembler &Asm) const final;
  void emitOffset(Assembler *Asm) const;
};

/// Handles encoding of branch and link to global location.
class BlRelocatableFixup final : public AssemblerFixup {
  BlRelocatableFixup(const BlRelocatableFixup &) = delete;
  BlRelocatableFixup &operator=(const BlRelocatableFixup &) = delete;

public:
  BlRelocatableFixup() = default;
  size_t emit(GlobalContext *Ctx, const Assembler &Asm) const final;
  void emitOffset(Assembler *Asm) const;
};

class AssemblerARM32 : public Assembler {
  AssemblerARM32(const AssemblerARM32 &) = delete;
  AssemblerARM32 &operator=(const AssemblerARM32 &) = delete;

public:
  // Rotation values.
  enum RotationValue {
    kRotateNone, // Omitted
    kRotate8,    // ror #8
    kRotate16,   // ror #16
    kRotate24    // ror #24
  };

  // Encoding of the number of D registers in a list of D registers.
  enum DRegListSize {
    DRegListSize1 = 7,  // 0b0111
    DRegListSize2 = 10, // 0b1010
    DRegListSIze3 = 6,  // 0b0110
    DRegListSize4 = 2   // 0b0010
  };

  class TargetInfo {
    TargetInfo(const TargetInfo &) = delete;
    TargetInfo &operator=(const TargetInfo &) = delete;

  public:
    TargetInfo(bool HasFramePointer, RegNumT FrameOrStackReg)
        : HasFramePointer(HasFramePointer), FrameOrStackReg(FrameOrStackReg) {}
    explicit TargetInfo(const TargetLowering *Target)
        : HasFramePointer(Target->hasFramePointer()),
          FrameOrStackReg(Target->getFrameOrStackReg()) {}
    const bool HasFramePointer;
    const RegNumT FrameOrStackReg;
  };

  explicit AssemblerARM32(bool IsNonsfi, bool use_far_branches = false)
      : Assembler(Asm_ARM32), IsNonsfi(IsNonsfi) {
    // TODO(kschimpf): Add mode if needed when branches are handled.
    (void)use_far_branches;
  }
  ~AssemblerARM32() override {
    if (BuildDefs::asserts()) {
      for (const Label *Label : CfgNodeLabels) {
        Label->finalCheck();
      }
      for (const Label *Label : LocalLabels) {
        Label->finalCheck();
      }
    }
  }

  MoveRelocatableFixup *createMoveFixup(bool IsMovW, const Constant *Value);

  BlRelocatableFixup *createBlFixup(const ConstantRelocatable *BlTarget);

  void alignFunction() override {
    const SizeT Align = 1 << getBundleAlignLog2Bytes();
    SizeT BytesNeeded = Utils::OffsetToAlignment(Buffer.getPosition(), Align);
    constexpr SizeT InstSize = sizeof(IValueT);
    assert(BytesNeeded % InstARM32::InstSize == 0);
    while (BytesNeeded > 0) {
      trap();
      BytesNeeded -= InstSize;
    }
  }

  SizeT getBundleAlignLog2Bytes() const override { return 4; }

  const char *getAlignDirective() const override { return ".p2alignl"; }

  llvm::ArrayRef<uint8_t> getNonExecBundlePadding() const override;

  void padWithNop(intptr_t Padding) override;

  Ice::Label *getCfgNodeLabel(SizeT NodeNumber) override {
    assert(NodeNumber < CfgNodeLabels.size());
    return CfgNodeLabels[NodeNumber];
  }

  Label *getOrCreateCfgNodeLabel(SizeT NodeNumber) {
    return getOrCreateLabel(NodeNumber, CfgNodeLabels);
  }

  Label *getOrCreateLocalLabel(SizeT Number) {
    return getOrCreateLabel(Number, LocalLabels);
  }

  void bindLocalLabel(const InstARM32Label *InstL, SizeT Number) {
    if (BuildDefs::dump() && !getFlags().getDisableHybridAssembly()) {
      constexpr SizeT InstSize = 0;
      emitTextInst(InstL->getLabelName() + ":", InstSize);
    }
    Label *L = getOrCreateLocalLabel(Number);
    if (!getPreliminary())
      this->bind(L);
  }

  bool fixupIsPCRel(FixupKind Kind) const override {
    if (Kind == llvm::ELF::R_ARM_MOVW_PREL_NC)
      return true;
    if (Kind == llvm::ELF::R_ARM_MOVT_PREL)
      return true;
    return false;
  }

  /// Accessors to keep track of the number of bytes generated inside
  /// InstARM32::emit() methods, when run inside of
  /// InstARM32::emitUsingTextFixup().
  void resetEmitTextSize() { EmitTextSize = 0; }
  void incEmitTextSize(size_t Amount) { EmitTextSize += Amount; }
  void decEmitTextSize(size_t Amount) { EmitTextSize -= Amount; }
  size_t getEmitTextSize() const { return EmitTextSize; }

  void bind(Label *label);

  // List of instructions implemented by integrated assembler.

  void adc(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void add(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void and_(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
            bool SetFlags, CondARM32::Cond Cond);

  void asr(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void b(Label *L, CondARM32::Cond Cond);

  void bkpt(uint16_t Imm16);

  void bic(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void bl(const ConstantRelocatable *Target);

  void blx(const Operand *Target);

  void bx(RegARM32::GPRRegister Rm, CondARM32::Cond Cond = CondARM32::AL);

  void clz(const Operand *OpRd, const Operand *OpSrc, CondARM32::Cond Cond);

  void cmn(const Operand *OpRn, const Operand *OpSrc1, CondARM32::Cond Cond);

  void cmp(const Operand *OpRn, const Operand *OpSrc1, CondARM32::Cond Cond);

  void dmb(IValueT Option); // Option is a 4-bit value.

  void eor(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void ldr(const Operand *OpRt, const Operand *OpAddress, CondARM32::Cond Cond,
           const TargetInfo &TInfo);

  void ldr(const Operand *OpRt, const Operand *OpAddress, CondARM32::Cond Cond,
           const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    ldr(OpRt, OpAddress, Cond, TInfo);
  }

  void ldrex(const Operand *OpRt, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void ldrex(const Operand *OpRt, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    ldrex(OpRt, OpAddress, Cond, TInfo);
  }

  void lsl(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void lsr(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void mov(const Operand *OpRd, const Operand *OpSrc, CondARM32::Cond Cond);

  void movw(const Operand *OpRd, const Operand *OpSrc, CondARM32::Cond Cond);

  void movt(const Operand *OpRd, const Operand *OpSrc, CondARM32::Cond Cond);

  void mla(const Operand *OpRd, const Operand *OpRn, const Operand *OpRm,
           const Operand *OpRa, CondARM32::Cond Cond);

  void mls(const Operand *OpRd, const Operand *OpRn, const Operand *OpRm,
           const Operand *OpRa, CondARM32::Cond Cond);

  void mul(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void mvn(const Operand *OpRd, const Operand *OpScc, CondARM32::Cond Cond);

  void nop();

  void orr(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void pop(const Variable *OpRt, CondARM32::Cond Cond);

  // Note: Registers is a bitset, where bit n corresponds to register Rn.
  void popList(const IValueT Registers, CondARM32::Cond Cond);

  void push(const Operand *OpRt, CondARM32::Cond Cond);

  // Note: Registers is a bitset, where bit n corresponds to register Rn.
  void pushList(const IValueT Registers, CondARM32::Cond Cond);

  void rbit(const Operand *OpRd, const Operand *OpRm, CondARM32::Cond Cond);

  void rev(const Operand *OpRd, const Operand *OpRm, CondARM32::Cond Cond);

  void rsb(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void rsc(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void sbc(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  void sdiv(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
            CondARM32::Cond Cond);

  void str(const Operand *OpRt, const Operand *OpAddress, CondARM32::Cond Cond,
           const TargetInfo &TInfo);

  void str(const Operand *OpRt, const Operand *OpAddress, CondARM32::Cond Cond,
           const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    str(OpRt, OpAddress, Cond, TInfo);
  }

  void strex(const Operand *OpRd, const Operand *OpRt, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void strex(const Operand *OpRd, const Operand *OpRt, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    strex(OpRd, OpRt, OpAddress, Cond, TInfo);
  }

  void sub(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
           bool SetFlags, CondARM32::Cond Cond);

  // Implements sxtb/sxth depending on type of OpSrc0.
  void sxt(const Operand *OpRd, const Operand *OpSrc0, CondARM32::Cond Cond);

  void trap();

  void tst(const Operand *OpRn, const Operand *OpSrc1, CondARM32::Cond Cond);

  void udiv(const Operand *OpRd, const Operand *OpRn, const Operand *OpSrc1,
            CondARM32::Cond Cond);

  void umull(const Operand *OpRdLo, const Operand *OpRdHi, const Operand *OpRn,
             const Operand *OpRm, CondARM32::Cond Cond);

  // Implements uxtb/uxth depending on type of OpSrc0.
  void uxt(const Operand *OpRd, const Operand *OpSrc0, CondARM32::Cond Cond);

  void vabss(const Operand *OpSd, const Operand *OpSm, CondARM32::Cond Cond);

  void vabsd(const Operand *OpDd, const Operand *OpDm, CondARM32::Cond Cond);

  void vabsq(const Operand *OpQd, const Operand *OpQm);

  void vaddd(const Operand *OpDd, const Operand *OpDn, const Operand *OpDm,
             CondARM32::Cond Cond);

  void vadds(const Operand *OpSd, const Operand *OpSn, const Operand *OpSm,
             CondARM32::Cond Cond);

  // Integer vector add.
  void vaddqi(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const Operand *OpQn);

  // Float vector add.
  void vaddqf(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vandq(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vbslq(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vceqqi(const Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const Operand *OpQn);

  void vceqqs(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vcgeqi(const Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const Operand *OpQn);

  void vcugeqi(const Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
               const Operand *OpQn);

  void vcgeqs(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vcgtqi(const Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const Operand *OpQn);

  void vcugtqi(const Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
               const Operand *OpQn);

  void vcgtqs(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vcmpd(const Operand *OpDd, const Operand *OpDm, CondARM32::Cond cond);

  // Second argument of compare is zero (+0.0).
  void vcmpdz(const Operand *OpDd, CondARM32::Cond cond);

  void vcmps(const Operand *OpSd, const Operand *OpSm, CondARM32::Cond cond);

  // Second argument of compare is zero (+0.0).
  void vcmpsz(const Operand *OpSd, CondARM32::Cond cond);

  void vcvtds(const Operand *OpDd, const Operand *OpSm, CondARM32::Cond Cond);

  // vcvt<c>.S32.F32
  void vcvtis(const Operand *OpSd, const Operand *OpSm, CondARM32::Cond Cond);

  // vcvt<c>.S32.F64
  void vcvtid(const Operand *OpSd, const Operand *OpDm, CondARM32::Cond Cond);

  // vcvt<c>.F64.S32
  void vcvtdi(const Operand *OpDd, const Operand *OpSm, CondARM32::Cond Cond);

  // vcvt<c>.F64.U32
  void vcvtdu(const Operand *OpDd, const Operand *OpSm, CondARM32::Cond Cond);

  void vcvtsd(const Operand *OpSd, const Operand *OpDm, CondARM32::Cond Cond);

  // vcvt<c>.F32.S32
  void vcvtsi(const Operand *OpSd, const Operand *OpSm, CondARM32::Cond Cond);

  // vcvt<c>.F32.U32
  void vcvtsu(const Operand *OpSd, const Operand *OpSm, CondARM32::Cond Cond);

  // vcvt<c>.U32.F64
  void vcvtud(const Operand *OpSd, const Operand *OpDm, CondARM32::Cond Cond);

  // vcvt<c>.u32.f32
  void vcvtus(const Operand *OpSd, const Operand *OpSm, CondARM32::Cond Cond);

  void vcvtqsi(const Operand *OpQd, const Operand *OpQm);

  void vcvtqsu(const Operand *OpQd, const Operand *OpQm);

  void vcvtqis(const Operand *OpQd, const Operand *OpQm);

  void vcvtqus(const Operand *OpQd, const Operand *OpQm);

  void vdivd(const Operand *OpDd, const Operand *OpDn, const Operand *OpDm,
             CondARM32::Cond Cond);

  void vdivs(const Operand *OpSd, const Operand *OpSn, const Operand *OpSm,
             CondARM32::Cond Cond);

  void veord(const Operand *OpDd, const Operand *OpDn, const Operand *OpDm);

  void veorq(const Operand *OpQd, const Operand *OpQn, const Operand *OpQm);

  void vldrd(const Operand *OpDd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void vldrd(const Operand *OpDd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vldrd(OpDd, OpAddress, Cond, TInfo);
  }

  void vldrs(const Operand *OpSd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void vldrs(const Operand *OpSd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vldrs(OpSd, OpAddress, Cond, TInfo);
  }

  void vldrq(const Operand *OpQd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void vldrq(const Operand *OpQd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vldrq(OpQd, OpAddress, Cond, TInfo);
  }

  // ElmtSize = #bits in vector element.
  void vld1qr(size_t ElmtSize, const Operand *OpQd, const Operand *OpRn,
              const TargetInfo &TInfo);

  void vld1(size_t ElmtSize, const Operand *OpQd, const Operand *OpRn,
            const TargetInfo &TInfo);

  void vld1qr(size_t ElmtSize, const Operand *OpQd, const Operand *OpRn,
              const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vld1qr(ElmtSize, OpQd, OpRn, TInfo);
  }

  void vld1(size_t ElmtSize, const Operand *OpQd, const Operand *OpRn,
            const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vld1(ElmtSize, OpQd, OpRn, TInfo);
  }

  // Qn[i] = Imm for all i in vector. Returns true iff Imm can be defined as an
  // Imm8 using AdvSIMDExpandImm().
  bool vmovqc(const Operand *OpQd, const ConstantInteger32 *Imm);

  // Dn = FpImm
  void vmovd(const Operand *OpDn, const OperandARM32FlexFpImm *OpFpImm,
             CondARM32::Cond Cond);

  // Dd = Dm
  void vmovdd(const Operand *OpDd, const Variable *OpDm, CondARM32::Cond Cond);

  // Dm = Rt:Rt2
  void vmovdrr(const Operand *OpDm, const Operand *OpRt, const Operand *OpRt2,
               CondARM32::Cond Cond);

  // Qd[Index] = Rt
  void vmovqir(const Operand *OpQd, uint32_t Index, const Operand *OpRt,
               CondARM32::Cond Cond);

  // Qd[Index] = Sm
  void vmovqis(const Operand *OpQd, uint32_t Indx, const Operand *OpSm,
               CondARM32::Cond Cond);

  // Rt = Qm[Index]
  void vmovrqi(const Operand *OpRt, const Operand *OpQd, uint32_t Index,
               CondARM32::Cond Cond);

  // Rt:Rt2 = Dm
  void vmovrrd(const Operand *OpRt, const Operand *OpRt2, const Operand *OpDm,
               CondARM32::Cond Cond);

  // Rt = Sn
  void vmovrs(const Operand *OpRt, const Operand *OpSn, CondARM32::Cond Cond);

  // Sn = FpImm
  void vmovs(const Operand *OpSn, const OperandARM32FlexFpImm *OpFpImm,
             CondARM32::Cond Cond);

  // Sd = Sm
  void vmovss(const Operand *OpSd, const Variable *OpSm, CondARM32::Cond Cond);

  // Sd = Qm[Index]
  void vmovsqi(const Operand *OpSd, const Operand *OpQm, uint32_t Index,
               CondARM32::Cond Cond);

  // Sn = Rt
  void vmovsr(const Operand *OpSn, const Operand *OpRt, CondARM32::Cond Cond);

  void vmlad(const Operand *OpDd, const Operand *OpDn, const Operand *OpDm,
             CondARM32::Cond Cond);

  void vmlas(const Operand *OpSd, const Operand *OpSn, const Operand *OpSm,
             CondARM32::Cond Cond);

  void vmlsd(const Operand *OpDd, const Operand *OpDn, const Operand *OpDm,
             CondARM32::Cond Cond);

  void vmlss(const Operand *OpSd, const Operand *OpSn, const Operand *OpSm,
             CondARM32::Cond Cond);

  // Uses APSR_nzcv as register
  void vmrsAPSR_nzcv(CondARM32::Cond Cond);

  void vmuld(const Operand *OpDd, const Operand *OpDn, const Operand *OpDm,
             CondARM32::Cond Cond);

  // Integer vector multiply.
  void vmulqi(Type ElmtTy, const Operand *OpQd, const Operand *OpQn,
              const Operand *OpQm);

  // Integer vector multiply high.
  void vmulh(Type ElmtTy, const Operand *OpQd, const Operand *OpQn,
             const Operand *OpQm, bool Unsigned);

  // Integer vector multiply add pairwise.
  void vmlap(Type ElmtTy, const Operand *OpQd, const Operand *OpQn,
             const Operand *OpQm);

  // Vector element replication.
  void vdup(Type ElmtTy, const Operand *OpQd, const Operand *OpQn, IValueT Idx);

  // Vector interleave lower halves.
  void vzip(Type ElmtTy, const Operand *OpQd, const Operand *OpQn,
            const Operand *OpQm);

  // Float vector multiply.
  void vmulqf(const Operand *OpQd, const Operand *OpQn, const Operand *OpQm);

  void vmuls(const Operand *OpSd, const Operand *OpSn, const Operand *OpSm,
             CondARM32::Cond Cond);

  void vmvnq(const Operand *OpQd, const Operand *OpQm);

  void vmovlq(const Operand *OpQd, const Operand *OpQn, const Operand *OpQm);
  void vmovhq(const Operand *OpQd, const Operand *OpQn, const Operand *OpQm);
  void vmovhlq(const Operand *OpQd, const Operand *OpQn, const Operand *OpQm);
  void vmovlhq(const Operand *OpQd, const Operand *OpQn, const Operand *OpQm);

  void vnegqs(const Operand *OpQd, const Operand *OpQm);

  void vnegqs(Type ElmtTy, const Operand *OpQd, const Operand *OpQm);

  void vorrq(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vpop(const Variable *OpBaseReg, SizeT NumConsecRegs,
            CondARM32::Cond Cond);

  void vpush(const Variable *OpBaseReg, SizeT NumConsecRegs,
             CondARM32::Cond Cond);

  void vshlqi(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const Operand *OpQn);

  void vshlqu(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const Operand *OpQn);

  void vshlqc(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const ConstantInteger32 *OpQn);

  void vshrqc(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const ConstantInteger32 *OpQn, InstARM32::FPSign Sign);

  void vsqrtd(const Operand *OpDd, const Operand *OpDm, CondARM32::Cond Cond);

  void vsqrts(const Operand *OpSd, const Operand *OpSm, CondARM32::Cond Cond);

  void vstrd(const Operand *OpDd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void vstrd(const Operand *OpDd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vstrd(OpDd, OpAddress, Cond, TInfo);
  }

  void vstrs(const Operand *OpSd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void vstrs(const Operand *OpSd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vstrs(OpSd, OpAddress, Cond, TInfo);
  }

  void vstrq(const Operand *OpQd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetInfo &TInfo);

  void vstrq(const Operand *OpQd, const Operand *OpAddress,
             CondARM32::Cond Cond, const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vstrq(OpQd, OpAddress, Cond, TInfo);
  }

  // ElmtSize = #bits in vector element.
  void vst1qr(size_t ElmtSize, const Operand *OpQd, const Operand *OpAddress,
              const TargetInfo &TInfo);

  void vst1qr(size_t ElmtSize, const Operand *OpQd, const Operand *OpRn,
              const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vst1qr(ElmtSize, OpQd, OpRn, TInfo);
  }

  void vst1(size_t ElmtSize, const Operand *OpQd, const Operand *OpAddress,
            const TargetInfo &TInfo);

  void vst1(size_t ElmtSize, const Operand *OpQd, const Operand *OpRn,
            const TargetLowering *Lowering) {
    const TargetInfo TInfo(Lowering);
    vst1(ElmtSize, OpQd, OpRn, TInfo);
  }

  void vsubd(const Operand *OpDd, const Operand *OpDn, const Operand *OpDm,
             CondARM32::Cond Cond);

  // Integer vector subtract.
  void vsubqi(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
              const Operand *OpQn);

  // Integer vector saturating subtract.
  void vqsubqi(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
               const Operand *OpQn);
  void vqsubqu(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
               const Operand *OpQn);

  // Integer vector saturating add.
  void vqaddqi(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
               const Operand *OpQn);
  void vqaddqu(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
               const Operand *OpQn);

  // Integer vector packing with optional saturation.
  void vqmovn2(Type ElmtTy, const Operand *OpQd, const Operand *OpQm,
               const Operand *OpQn, bool Unsigned, bool Saturating);

  // Float vector subtract
  void vsubqf(const Operand *OpQd, const Operand *OpQm, const Operand *OpQn);

  void vsubs(const Operand *OpSd, const Operand *OpSn, const Operand *OpSm,
             CondARM32::Cond Cond);

  static bool classof(const Assembler *Asm) {
    return Asm->getKind() == Asm_ARM32;
  }

  void emitTextInst(const std::string &Text, SizeT InstSize);

private:
  ENABLE_MAKE_UNIQUE;

  const bool IsNonsfi;

  // A vector of pool-allocated x86 labels for CFG nodes.
  using LabelVector = std::vector<Label *>;
  LabelVector CfgNodeLabels;
  // A vector of pool-allocated x86 labels for Local labels.
  LabelVector LocalLabels;
  // Number of bytes emitted by InstARM32::emit() methods, when run inside
  // InstARM32::emitUsingTextFixup().
  size_t EmitTextSize = 0;

  // Load/store multiple addressing mode.
  enum BlockAddressMode {
    // bit encoding P U W
    DA = (0 | 0 | 0) << 21,   // decrement after
    IA = (0 | 4 | 0) << 21,   // increment after
    DB = (8 | 0 | 0) << 21,   // decrement before
    IB = (8 | 4 | 0) << 21,   // increment before
    DA_W = (0 | 0 | 1) << 21, // decrement after with writeback to base
    IA_W = (0 | 4 | 1) << 21, // increment after with writeback to base
    DB_W = (8 | 0 | 1) << 21, // decrement before with writeback to base
    IB_W = (8 | 4 | 1) << 21  // increment before with writeback to base
  };

  Label *getOrCreateLabel(SizeT Number, LabelVector &Labels);

  void bindCfgNodeLabel(const CfgNode *Node) override;

  // SIMD encoding for the vector ElmtTy.
  static IValueT encodeElmtType(Type ElmtTy);

  void emitInst(IValueT Value) {
    AssemblerBuffer::EnsureCapacity _(&Buffer);
    Buffer.emit<IValueT>(Value);
  }

  // List of possible checks to apply when calling emitType01() (below).
  enum EmitChecks { NoChecks, RdIsPcAndSetFlags };

  // Pattern cccctttoooosnnnnddddiiiiiiiiiiii where cccc=Cond, ttt=InstType,
  // s=SetFlags, oooo=Opcode, nnnn=Rn, dddd=Rd, iiiiiiiiiiii=imm12 (See ARM
  // section A5.2.3).
  void emitType01(CondARM32::Cond Cond, IValueT InstType, IValueT Opcode,
                  bool SetFlags, IValueT Rn, IValueT Rd, IValueT imm12,
                  EmitChecks RuleChecks, const char *InstName);

  // Converts appropriate representation on a data operation, and then calls
  // emitType01 above.
  void emitType01(CondARM32::Cond Cond, IValueT Opcode, const Operand *OpRd,
                  const Operand *OpRn, const Operand *OpSrc1, bool SetFlags,
                  EmitChecks RuleChecks, const char *InstName);

  // Same as above, but the value for Rd and Rn have already been converted
  // into instruction values.
  void emitType01(CondARM32::Cond Cond, IValueT Opcode, IValueT OpRd,
                  IValueT OpRn, const Operand *OpSrc1, bool SetFlags,
                  EmitChecks RuleChecks, const char *InstName);

  void emitType05(CondARM32::Cond Cond, int32_t Offset, bool Link);

  // Emit ccccoooaabalnnnnttttaaaaaaaaaaaa where cccc=Cond,
  // ooo=InstType, l=isLoad, b=isByte, and
  // aaa0a0aaaa0000aaaaaaaaaaaa=Address. Note that Address is assumed to be
  // defined by decodeAddress() in IceAssemblerARM32.cpp.
  void emitMemOp(CondARM32::Cond Cond, IValueT InstType, bool IsLoad,
                 bool IsByte, IValueT Rt, IValueT Address);

  // Emit ccccxxxxxxxxxxxxddddxxxxxxxxmmmm where cccc=Cond,
  // xxxxxxxxxxxx0000xxxxxxxx0000=Opcode, dddd=Rd, and mmmm=Rm.
  void emitRdRm(CondARM32::Cond Cond, IValueT Opcode, const Operand *OpRd,
                const Operand *OpRm, const char *InstName);

  // Emit ldr/ldrb/str/strb instruction with given address.
  void emitMemOp(CondARM32::Cond Cond, bool IsLoad, bool IsByte, IValueT Rt,
                 const Operand *OpAddress, const TargetInfo &TInfo,
                 const char *InstName);

  // Emit ldrh/ldrd/strh/strd instruction with given address using encoding 3.
  void emitMemOpEnc3(CondARM32::Cond Cond, IValueT Opcode, IValueT Rt,
                     const Operand *OpAddress, const TargetInfo &TInfo,
                     const char *InstName);

  // Emit cccc00011xxlnnnndddd11111001tttt where cccc=Cond, xx encodes type
  // size, l=IsLoad, nnnn=Rn (as defined by OpAddress), and tttt=Rt.
  void emitMemExOp(CondARM32::Cond, Type Ty, bool IsLoad, const Operand *OpRd,
                   IValueT Rt, const Operand *OpAddress,
                   const TargetInfo &TInfo, const char *InstName);

  // Pattern cccc100aaaalnnnnrrrrrrrrrrrrrrrr where cccc=Cond,
  // aaaa<<21=AddressMode, l=IsLoad, nnnn=BaseReg, and
  // rrrrrrrrrrrrrrrr is bitset of Registers.
  void emitMultiMemOp(CondARM32::Cond Cond, BlockAddressMode AddressMode,
                      bool IsLoad, IValueT BaseReg, IValueT Registers);

  // Pattern ccccxxxxxDxxxxxxddddxxxxiiiiiiii where cccc=Cond, ddddD=BaseReg,
  // iiiiiiii=NumConsecRegs, and xxxxx0xxxxxx0000xxxx00000000=Opcode.
  void emitVStackOp(CondARM32::Cond Cond, IValueT Opcode,
                    const Variable *OpBaseReg, SizeT NumConsecRegs);

  // Pattern cccc111xxDxxxxxxdddd101xxxMxmmmm where cccc=Cond, ddddD=Sd,
  // Mmmmm=Dm, and xx0xxxxxxdddd000xxx0x0000=Opcode.
  void emitVFPsd(CondARM32::Cond Cond, IValueT Opcode, IValueT Sd, IValueT Dm);

  // Pattern cccc111xxDxxxxxxdddd101xxxMxmmmm where cccc=Cond, Ddddd=Dd,
  // mmmmM=Sm, and xx0xxxxxxdddd000xxx0x0000=Opcode.
  void emitVFPds(CondARM32::Cond Cond, IValueT Opcode, IValueT Dd, IValueT Sm);

  // Pattern 111100000D00nnnnddddttttssaammmm | Opcode where Ddddd=Dd, nnnn=Rn,
  // mmmmm=Rm, tttt=NumDRegs, ElmtSize in {8, 16, 32, 64) and defines ss, and
  // aa=Align.
  void emitVMem1Op(IValueT Opcode, IValueT Dd, IValueT Rn, IValueT Rm,
                   DRegListSize NumDRegs, size_t ElmtSize, IValueT Align,
                   const char *InstName);

  // Pattern 111100000D00nnnnddddss00aaaammmm | Opcode where Ddddd=Dd, nnnn=Rn,
  // mmmmm=Rm, ElmtSize in {8, 16, 32) and defines ss, and aa=Align.
  void emitVMem1Op(IValueT Opcode, IValueT Dd, IValueT Rn, IValueT Rm,
                   size_t ElmtSize, IValueT Align, const char *InstName);

  // Pattern cccc011100x1dddd1111mmmm0001nnn where cccc=Cond,
  // x=Opcode, dddd=Rd, nnnn=Rn, mmmm=Rm.
  void emitDivOp(CondARM32::Cond Cond, IValueT Opcode, IValueT Rd, IValueT Rn,
                 IValueT Rm);

  // cccc1110iiiennnntttt1011Njj10000 where cccc=Cond, tttt=Rt, Ndddd=2*Qn=Dn,
  // iii=Opcode1, jj=Opcode2, Opcode1Opcode2 encodes Index and the
  // corresponding element size of the vector element, and e=IsExtract.
  void emitInsertExtractInt(CondARM32::Cond Cond, const Operand *OpQn,
                            uint32_t Index, const Operand *OpRt, bool IsExtract,
                            const char *InstName);

  // cccc11101D110000dddd101001M0mmmm where cccc=Cond, ddddD=Sd, and mmmmM=Sm.
  // Assigns Sd the value of Sm.
  void emitMoveSS(CondARM32::Cond Cond, IValueT Sd, IValueT Sm);

  // Pattern ccccxxxxxxxfnnnnddddssss1001mmmm where cccc=Cond, dddd=Rd, nnnn=Rn,
  // mmmm=Rm, ssss=Rs, f=SetFlags and xxxxxxx=Opcode.
  void emitMulOp(CondARM32::Cond Cond, IValueT Opcode, IValueT Rd, IValueT Rn,
                 IValueT Rm, IValueT Rs, bool SetFlags);

  // Pattern cccc0001101s0000ddddxxxxxtt0mmmm where cccc=Cond, s=SetFlags,
  // dddd=Rd, mmmm=Rm, tt=Shift, and xxxxx is defined by OpSrc1. OpSrc1 defines
  // either xxxxx=Imm5, or xxxxx=ssss0 where ssss=Rs.
  void emitShift(const CondARM32::Cond Cond,
                 const OperandARM32::ShiftKind Shift, const Operand *OpRd,
                 const Operand *OpRm, const Operand *OpSrc1,
                 const bool SetFlags, const char *InstName);

  // Implements various forms of signed/unsigned extend value, using pattern
  // ccccxxxxxxxxnnnnddddrr000111mmmm where cccc=Cond, xxxxxxxx<<20=Opcode,
  // nnnn=Rn, dddd=Rd, rr=Rotation, and mmmm=Rm.
  void emitSignExtend(CondARM32::Cond, IValueT Opcode, const Operand *OpRd,
                      const Operand *OpSrc0, const char *InstName);

  // Implements various forms of vector (SIMD) operations.  Implements pattern
  // 111100100D00nnnndddn00F0NQM0mmmm where Dddd=Dd, Nnnn=Dn, Mmmm=Dm,
  // Q=UseQRegs, F=IsFloatTy, and Opcode is unioned into the pattern.
  void emitSIMDBase(IValueT Opcode, IValueT Dd, IValueT Dn, IValueT Dm,
                    bool UseQRegs, bool IsFloatTy);

  // Same as emitSIMDBase above, except ElmtShift=20 and ElmtSize is computed
  // from ElmtTy.
  void emitSIMD(IValueT Opcode, Type ElmtTy, IValueT Dd, IValueT Dn, IValueT Dm,
                bool UseQRegs);

  // Implements various integer forms of vector (SIMD) operations using Q
  // registers. Implements pattern 111100100D00nnn0ddd000F0N1M0mmm0 where
  // Dddd=Qd, Nnnn=Qn, Mmmm=Qm, F=IsFloatTy, and Opcode is unioned into the
  // pattern.
  void emitSIMDqqqBase(IValueT Opcode, const Operand *OpQd, const Operand *OpQn,
                       const Operand *OpQm, bool IsFloatTy,
                       const char *OpcodeName);

  // Same as emitSIMD above, except ElmtShift=20 and ElmtSize is computed from
  // ElmtTy.
  void emitSIMDqqq(IValueT Opcode, Type ElmtTy, const Operand *OpQd,
                   const Operand *OpQn, const Operand *OpQm,
                   const char *OpcodeName);

  // Implements various forms of vector (SIMD) shifts using Q registers.
  // Implements pattern 111100101Diiiiiidddd010101M1mmmm where Dddd=Qd, Mmmm=Qm,
  // iiiiii=Imm6, and Opcode is unioned into the pattern.
  void emitSIMDShiftqqc(IValueT Opcode, const Operand *OpQd,
                        const Operand *OpQm, const IValueT Imm6,
                        const char *OpcodeName);

  // Implements various forms of vector (SIMD) casts between (signed and
  // unsigned) integer and floating point types (f32). Implements pattern
  // 111100111D11ss11dddd011ooQM0mmmm where Dddd=Qd, Mmmm=Qm, 10=ss, op=00, 1=Q,
  // and Opcode is unioned into the pattern.
  void emitSIMDCvtqq(IValueT Opcode, const Operand *OpQd, const Operand *OpQm,
                     const char *CvtName);

  // Pattern cccctttxxxxnnnn0000iiiiiiiiiiii where cccc=Cond, nnnn=Rn,
  // ttt=Instruction type (derived from OpSrc1), iiiiiiiiiiii is derived from
  // OpSrc1, and xxxx=Opcode.
  void emitCompareOp(CondARM32::Cond Cond, IValueT Opcode, const Operand *OpRn,
                     const Operand *OpSrc1, const char *CmpName);

  void emitBranch(Label *L, CondARM32::Cond, bool Link);

  // Returns the offset encoded in the branch instruction Inst.
  static IOffsetT decodeBranchOffset(IValueT Inst);

  // Implements movw/movt, generating pattern ccccxxxxxxxsiiiiddddiiiiiiiiiiii
  // where cccc=Cond, xxxxxxx<<21=Opcode, dddd=Rd, s=SetFlags, and
  // iiiiiiiiiiiiiiii=Imm16.
  void emitMovwt(CondARM32::Cond Cond, bool IsMovw, const Operand *OpRd,
                 const Operand *OpSrc, const char *MovName);

  // Emit VFP instruction with 3 D registers.
  void emitVFPddd(CondARM32::Cond Cond, IValueT Opcode, const Operand *OpDd,
                  const Operand *OpDn, const Operand *OpDm,
                  const char *InstName);

  void emitVFPddd(CondARM32::Cond Cond, IValueT Opcode, IValueT Dd, IValueT Dn,
                  IValueT Dm);

  // Emit VFP instruction with 3 S registers.
  void emitVFPsss(CondARM32::Cond Cond, IValueT Opcode, IValueT Sd, IValueT Sn,
                  IValueT Sm);

  void emitVFPsss(CondARM32::Cond Cond, IValueT Opcode, const Operand *OpSd,
                  const Operand *OpSn, const Operand *OpSm,
                  const char *InstName);
};

} // end of namespace ARM32
} // end of namespace Ice

#endif // SUBZERO_SRC_ICEASSEMBLERARM32_H
