/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef jit_mips_shared_MacroAssembler_mips_shared_h
#define jit_mips_shared_MacroAssembler_mips_shared_h

#include "jit/AtomicOp.h"
#include "jit/mips64/Assembler-mips64.h"

namespace js {
namespace jit {

enum LoadStoreSize {
  SizeByte = 8,
  SizeHalfWord = 16,
  SizeWord = 32,
  SizeDouble = 64
};

enum LoadStoreExtension { ZeroExtend = 0, SignExtend = 1 };

enum JumpKind { LongJump = 0, ShortJump = 1 };

enum DelaySlotFill { DontFillDelaySlot = 0, FillDelaySlot = 1 };

static constexpr Register CallReg = t9;

class MacroAssemblerMIPSShared : public Assembler {
 protected:
  // Perform a downcast. Should be removed by Bug 996602.
  MacroAssembler& asMasm();
  const MacroAssembler& asMasm() const;

  Condition ma_cmp(Register rd, Register lhs, Register rhs, Condition c);
  Condition ma_cmp(Register rd, Register lhs, Imm32 imm, Condition c);

  void compareFloatingPoint(FloatFormat fmt, FloatRegister lhs,
                            FloatRegister rhs, DoubleCondition c,
                            FloatTestKind* testKind, FPConditionBit fcc = FCC0);

 public:
  void ma_move(Register rd, Register rs);

  void ma_li(Register dest, ImmGCPtr ptr);

  void ma_li(Register dest, Imm32 imm);
  void ma_liPatchable(Register dest, Imm32 imm);

  // Shift operations
  void ma_sll(Register rd, Register rt, Imm32 shift);
  void ma_srl(Register rd, Register rt, Imm32 shift);
  void ma_sra(Register rd, Register rt, Imm32 shift);
  void ma_ror(Register rd, Register rt, Imm32 shift);
  void ma_rol(Register rd, Register rt, Imm32 shift);

  void ma_sll(Register rd, Register rt, Register shift);
  void ma_srl(Register rd, Register rt, Register shift);
  void ma_sra(Register rd, Register rt, Register shift);
  void ma_ror(Register rd, Register rt, Register shift);
  void ma_rol(Register rd, Register rt, Register shift);

  // Negate
  void ma_negu(Register rd, Register rs);

  void ma_not(Register rd, Register rs);

  // Bit extract/insert
  void ma_ext(Register rt, Register rs, uint16_t pos, uint16_t size);
  void ma_ins(Register rt, Register rs, uint16_t pos, uint16_t size);

  // Sign extend
  void ma_seb(Register rd, Register rt);
  void ma_seh(Register rd, Register rt);

  // and
  void ma_and(Register rd, Register rs);
  void ma_and(Register rd, Imm32 imm);
  void ma_and(Register rd, Register rs, Imm32 imm);

  // or
  void ma_or(Register rd, Register rs);
  void ma_or(Register rd, Imm32 imm);
  void ma_or(Register rd, Register rs, Imm32 imm);

  // xor
  void ma_xor(Register rd, Register rs);
  void ma_xor(Register rd, Imm32 imm);
  void ma_xor(Register rd, Register rs, Imm32 imm);

  // word swap byte within halfwords
  void ma_wsbh(Register rd, Register rt);

  void ma_ctz(Register rd, Register rs);

  // load
  FaultingCodeOffset ma_load(Register dest, const BaseIndex& src,
                             LoadStoreSize size = SizeWord,
                             LoadStoreExtension extension = SignExtend);
  void ma_load_unaligned(Register dest, const BaseIndex& src,
                         LoadStoreSize size = SizeWord,
                         LoadStoreExtension extension = SignExtend);
  void ma_load_unaligned(Register dest, const Address& address,
                         LoadStoreSize size = SizeWord,
                         LoadStoreExtension extension = SignExtend);
  void ma_load_unaligned(const wasm::MemoryAccessDesc& access, Register dest,
                         const BaseIndex& src, Register temp,
                         LoadStoreSize size, LoadStoreExtension extension);

  // store
  FaultingCodeOffset ma_store(Register data, const BaseIndex& dest,
                              LoadStoreSize size = SizeWord,
                              LoadStoreExtension extension = SignExtend);
  void ma_store(Imm32 imm, const BaseIndex& dest, LoadStoreSize size = SizeWord,
                LoadStoreExtension extension = SignExtend);
  void ma_store_unaligned(Register data, const Address& dest,
                          LoadStoreSize size = SizeWord);
  void ma_store_unaligned(Register data, const BaseIndex& dest,
                          LoadStoreSize size = SizeWord);
  void ma_store_unaligned(const wasm::MemoryAccessDesc& access, Register data,
                          const BaseIndex& dest, Register temp,
                          LoadStoreSize size, LoadStoreExtension extension);

  // arithmetic based ops
  // add
  void ma_addu(Register rd, Register rs, Imm32 imm);
  void ma_addu(Register rd, Register rs);
  void ma_addu(Register rd, Imm32 imm);
  void ma_add32TestCarry(Condition cond, Register rd, Register rs, Register rt,
                         Label* overflow);
  void ma_add32TestCarry(Condition cond, Register rd, Register rs, Imm32 imm,
                         Label* overflow);

  // subtract
  void ma_subu(Register rd, Register rs, Imm32 imm);
  void ma_subu(Register rd, Register rs);
  void ma_subu(Register rd, Imm32 imm);
  void ma_sub32TestOverflow(Register rd, Register rs, Imm32 imm,
                            Label* overflow);

  // multiplies.  For now, there are only few that we care about.
  void ma_mul(Register rd, Register rs, Imm32 imm);
  void ma_mul32TestOverflow(Register rd, Register rs, Register rt,
                            Label* overflow);
  void ma_mul32TestOverflow(Register rd, Register rs, Imm32 imm,
                            Label* overflow);

  // fast mod, uses scratch registers, and thus needs to be in the assembler
  // implicitly assumes that we can overwrite dest at the beginning of the
  // sequence
  void ma_mod_mask(Register src, Register dest, Register hold, Register remain,
                   int32_t shift, Label* negZero = nullptr);

  // branches when done from within mips-specific code
  void ma_b(Register lhs, Register rhs, Label* l, Condition c,
            JumpKind jumpKind = LongJump);
  void ma_b(Register lhs, Imm32 imm, Label* l, Condition c,
            JumpKind jumpKind = LongJump);
  void ma_b(Register lhs, ImmPtr imm, Label* l, Condition c,
            JumpKind jumpKind = LongJump);
  void ma_b(Register lhs, ImmGCPtr imm, Label* l, Condition c,
            JumpKind jumpKind = LongJump) {
    UseScratchRegisterScope temps(*this);
    Register scratch = temps.Acquire();
    MOZ_ASSERT(lhs != scratch);
    ma_li(scratch, imm);
    ma_b(lhs, scratch, l, c, jumpKind);
  }

  void ma_b(Label* l, JumpKind jumpKind = LongJump);

  // fp instructions
  void ma_lis(FloatRegister dest, float value);

  FaultingCodeOffset ma_sd(FloatRegister src, BaseIndex address);
  FaultingCodeOffset ma_ss(FloatRegister src, BaseIndex address);

  FaultingCodeOffset ma_ld(FloatRegister dest, const BaseIndex& src);
  FaultingCodeOffset ma_ls(FloatRegister dest, const BaseIndex& src);

  // FP branches
  void ma_bc1s(FloatRegister lhs, FloatRegister rhs, Label* label,
               DoubleCondition c, JumpKind jumpKind = LongJump,
               FPConditionBit fcc = FCC0);
  void ma_bc1d(FloatRegister lhs, FloatRegister rhs, Label* label,
               DoubleCondition c, JumpKind jumpKind = LongJump,
               FPConditionBit fcc = FCC0);

  void ma_call(ImmPtr dest);

  void ma_jump(ImmPtr dest);

  void ma_cmp_set(Register dst, Register lhs, Register rhs, Condition c);
  void ma_cmp_set(Register dst, Register lhs, Imm32 imm, Condition c);
  // void ma_cmp_set(Register dst, Address address, Imm32 imm, Condition c);
  void ma_cmp_set_double(Register dst, FloatRegister lhs, FloatRegister rhs,
                         DoubleCondition c);
  void ma_cmp_set_float32(Register dst, FloatRegister lhs, FloatRegister rhs,
                          DoubleCondition c);

  void moveToFloat32(Register src, FloatRegister dest) { as_mtc1(src, dest); }
  void moveFromFloat32(FloatRegister src, Register dest) { as_mfc1(dest, src); }

  void minMax32(Register lhs, Register rhs, Register dest, bool isMax);
  void minMax32(Register lhs, Imm32 rhs, Register dest, bool isMax);

  void minMaxPtr(Register lhs, Register rhs, Register dest, bool isMax);
  void minMaxPtr(Register lhs, ImmWord rhs, Register dest, bool isMax);

  // Evaluate srcDest = minmax<isMax>{Float32,Double}(srcDest, other).
  // Handle NaN specially if handleNaN is true.
  void minMaxDouble(FloatRegister srcDest, FloatRegister other, bool handleNaN,
                    bool isMax);
  void minMaxFloat32(FloatRegister srcDest, FloatRegister other, bool handleNaN,
                     bool isMax);

  FaultingCodeOffset loadDouble(const Address& addr, FloatRegister dest);
  FaultingCodeOffset loadDouble(const BaseIndex& src, FloatRegister dest);

  FaultingCodeOffset loadFloat32(const Address& addr, FloatRegister dest);
  FaultingCodeOffset loadFloat32(const BaseIndex& src, FloatRegister dest);

  FaultingCodeOffset loadFloat16(const Address& addr, FloatRegister dest,
                                 Register) {
    MOZ_CRASH("Not supported for this target");
  }
  FaultingCodeOffset loadFloat16(const BaseIndex& src, FloatRegister dest,
                                 Register) {
    MOZ_CRASH("Not supported for this target");
  }

  void outOfLineWasmTruncateToInt32Check(
      FloatRegister input, Register output, MIRType fromType, TruncFlags flags,
      Label* rejoin, const wasm::TrapSiteDesc& trapSiteDesc);
  void outOfLineWasmTruncateToInt64Check(
      FloatRegister input, Register64 output, MIRType fromType,
      TruncFlags flags, Label* rejoin, const wasm::TrapSiteDesc& trapSiteDesc);

 protected:
  void wasmLoadImpl(const wasm::MemoryAccessDesc& access, Register memoryBase,
                    Register ptr, Register ptrScratch, AnyRegister output,
                    Register tmp);
  void wasmStoreImpl(const wasm::MemoryAccessDesc& access, AnyRegister value,
                     Register memoryBase, Register ptr, Register ptrScratch,
                     Register tmp);
};

}  // namespace jit
}  // namespace js

#endif /* jit_mips_shared_MacroAssembler_mips_shared_h */
