/* 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_x86_shared_Architecture_x86_h
#define jit_x86_shared_Architecture_x86_h

#if !defined(JS_CODEGEN_X86) && !defined(JS_CODEGEN_X64)
#  error "Unsupported architecture!"
#endif

#include <algorithm>
#include <bit>
#include <string.h>

#include "jit/shared/Architecture-shared.h"

#include "jit/x86-shared/Constants-x86-shared.h"

namespace js {
namespace jit {

#if defined(JS_CODEGEN_X86)
// These offsets are specific to nunboxing, and capture offsets into the
// components of a js::Value.
static const int32_t NUNBOX32_TYPE_OFFSET = 4;
static const int32_t NUNBOX32_PAYLOAD_OFFSET = 0;
#endif

#if defined(JS_CODEGEN_X64) && defined(_WIN64)
static constexpr uint32_t ShadowStackSpace = 32;
#else
static constexpr uint32_t ShadowStackSpace = 0;
#endif

static const uint32_t JumpImmediateRange = INT32_MAX;

class Registers {
 public:
  using Code = uint8_t;
  using Encoding = X86Encoding::RegisterID;

  // Content spilled during bailouts.
  union RegisterContent {
    uintptr_t r;
  };

#if defined(JS_CODEGEN_X86)
  using SetType = uint8_t;

  static const char* GetName(Code code) {
    return X86Encoding::GPRegName(Encoding(code));
  }

  static const uint32_t Total = 8;
  static const uint32_t TotalPhys = 8;
  static const uint32_t Allocatable = 7;

#elif defined(JS_CODEGEN_X64)
  using SetType = uint16_t;

  static const char* GetName(Code code) {
    static const char* const Names[] = {
        "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
        "r8",  "r9",  "r10", "r11", "r12", "r13", "r14", "r15"};
    return Names[code];
  }

  static const uint32_t Total = 16;
  static const uint32_t TotalPhys = 16;
  static const uint32_t Allocatable = 14;
#endif

  static uint32_t SetSize(SetType x) { return std::popcount(x); }
  static uint32_t FirstBit(SetType x) {
    MOZ_ASSERT(x);
    return std::countr_zero(x);
  }
  static uint32_t LastBit(SetType x) {
    MOZ_ASSERT(x);
    return std::bit_width(x) - 1;
  }

  static Code FromName(const char* name) {
    for (size_t i = 0; i < Total; i++) {
      if (strcmp(GetName(Code(i)), name) == 0) {
        return Code(i);
      }
    }
    return Invalid;
  }

  static const Encoding StackPointer = X86Encoding::rsp;
  static const Encoding Invalid = X86Encoding::invalid_reg;

  static const SetType AllMask = (1 << Total) - 1;

#if defined(JS_CODEGEN_X86)
  static const SetType ArgRegMask = 0;

  static const SetType VolatileMask = (1 << X86Encoding::rax) |
                                      (1 << X86Encoding::rcx) |
                                      (1 << X86Encoding::rdx);

  static const SetType WrapperMask = VolatileMask | (1 << X86Encoding::rbx);

  static const SetType SingleByteRegs =
      (1 << X86Encoding::rax) | (1 << X86Encoding::rcx) |
      (1 << X86Encoding::rdx) | (1 << X86Encoding::rbx);

  static const SetType NonAllocatableMask =
      (1 << X86Encoding::rsp) | (1 << X86Encoding::rbp);

  // Registers returned from a JS -> JS call.
  static const SetType JSCallMask =
      (1 << X86Encoding::rcx) | (1 << X86Encoding::rdx);

  // Registers returned from a JS -> C call.
  static const SetType CallMask = (1 << X86Encoding::rax);

#elif defined(JS_CODEGEN_X64)
  static const SetType ArgRegMask =
#  if !defined(_WIN64)
      (1 << X86Encoding::rdi) | (1 << X86Encoding::rsi) |
#  endif
      (1 << X86Encoding::rdx) | (1 << X86Encoding::rcx) |
      (1 << X86Encoding::r8) | (1 << X86Encoding::r9);

  static const SetType VolatileMask = (1 << X86Encoding::rax) | ArgRegMask |
                                      (1 << X86Encoding::r10) |
                                      (1 << X86Encoding::r11);

  static const SetType WrapperMask = VolatileMask;

  static const SetType SingleByteRegs = AllMask & ~(1 << X86Encoding::rsp);

  static const SetType NonAllocatableMask =
      (1 << X86Encoding::rsp) | (1 << X86Encoding::rbp) |
      (1 << X86Encoding::r11);  // This is ScratchReg.

  // Registers returned from a JS -> JS call.
  static const SetType JSCallMask = (1 << X86Encoding::rcx);

  // Registers returned from a JS -> C call.
  static const SetType CallMask = (1 << X86Encoding::rax);

#endif

  static const SetType NonVolatileMask =
      AllMask & ~VolatileMask & ~(1 << X86Encoding::rsp);

  static const SetType AllocatableMask = AllMask & ~NonAllocatableMask;
};

using PackedRegisterMask = Registers::SetType;

class FloatRegisters {
 public:
  using Encoding = X86Encoding::XMMRegisterID;

  // Observe that there is a Simd128 type on both x86 and x64 whether SIMD is
  // implemented/enabled or not, and that the RegisterContent union is large
  // enough for a V128 datum always.  Producers and consumers of a register dump
  // must be aware of this even if they don't need to save/restore values in the
  // high lanes of the SIMD registers.  See the DumpAllRegs() implementations,
  // for example.

  enum ContentType : uint8_t {
    Single,   // 32-bit float.
    Double,   // 64-bit double.
    Simd128,  // 128-bit Wasm SIMD type.
    NumTypes
  };

  // Content spilled during bailouts.
  union RegisterContent {
    float s;
    double d;
    uint8_t v128[16];
  };

  static const char* GetName(Encoding code) {
    return X86Encoding::XMMRegName(code);
  }

  static Encoding FromName(const char* name) {
    for (size_t i = 0; i < Total; i++) {
      if (strcmp(GetName(Encoding(i)), name) == 0) {
        return Encoding(i);
      }
    }
    return Invalid;
  }

  static const Encoding Invalid = X86Encoding::invalid_xmm;

#if defined(JS_CODEGEN_X86)
  static const uint32_t Total = 8 * NumTypes;
  static const uint32_t TotalPhys = 8;
  static const uint32_t Allocatable = 7;
  using SetType = uint32_t;
#elif defined(JS_CODEGEN_X64)
  static const uint32_t Total = 16 * NumTypes;
  static const uint32_t TotalPhys = 16;
  static const uint32_t Allocatable = 15;
  using SetType = uint64_t;
#endif

  static_assert(sizeof(SetType) * 8 >= Total,
                "SetType should be large enough to enumerate all registers.");

  // Magic values which are used to duplicate a mask of physical register for
  // a specific type of register. A multiplication is used to copy and shift
  // the bits of the physical register mask.
  static const SetType SpreadSingle = SetType(1)
                                      << (uint32_t(Single) * TotalPhys);
  static const SetType SpreadDouble = SetType(1)
                                      << (uint32_t(Double) * TotalPhys);
  static const SetType SpreadSimd128 = SetType(1)
                                       << (uint32_t(Simd128) * TotalPhys);
  static const SetType SpreadScalar = SpreadSingle | SpreadDouble;
  static const SetType SpreadVector = SpreadSimd128;
  static const SetType Spread = SpreadScalar | SpreadVector;

  static const SetType AllPhysMask = ((1 << TotalPhys) - 1);
  static const SetType AllMask = AllPhysMask * Spread;
  static const SetType AllDoubleMask = AllPhysMask * SpreadDouble;
  static const SetType AllSingleMask = AllPhysMask * SpreadSingle;
  static const SetType AllVector128Mask = AllPhysMask * SpreadSimd128;

#if defined(JS_CODEGEN_X86)
  static const SetType NonAllocatableMask =
      Spread * (1 << X86Encoding::xmm7);  // This is ScratchDoubleReg.

#elif defined(JS_CODEGEN_X64)
  static const SetType NonAllocatableMask =
      Spread * (1 << X86Encoding::xmm15);  // This is ScratchDoubleReg.
#endif

#if defined(JS_CODEGEN_X64) && defined(_WIN64)
  static const SetType VolatileMask =
      ((1 << X86Encoding::xmm0) | (1 << X86Encoding::xmm1) |
       (1 << X86Encoding::xmm2) | (1 << X86Encoding::xmm3) |
       (1 << X86Encoding::xmm4) | (1 << X86Encoding::xmm5)) *
      Spread;
#else
  static const SetType VolatileMask = AllMask;
#endif

  static const SetType NonVolatileMask = AllMask & ~VolatileMask;
  static const SetType WrapperMask = VolatileMask;
  static const SetType AllocatableMask = AllMask & ~NonAllocatableMask;
};

static const uint32_t SpillSlotSize =
    std::max(sizeof(Registers::RegisterContent),
             sizeof(FloatRegisters::RegisterContent));

template <typename T>
class TypedRegisterSet;

struct FloatRegister {
  using Codes = FloatRegisters;
  using Code = size_t;
  using Encoding = Codes::Encoding;
  using SetType = Codes::SetType;
  static uint32_t SetSize(SetType x) {
    // Count the number of non-aliased registers, for the moment.
    //
    // Copy the set bits of each typed register to the low part of the of
    // the Set, and count the number of registers. This is made to avoid
    // registers which are allocated twice with different types (such as in
    // AllMask).
    x |= x >> (2 * Codes::TotalPhys);
    x |= x >> Codes::TotalPhys;
    x &= Codes::AllPhysMask;
    static_assert(Codes::AllPhysMask <= 0xffff,
                  "Optimizable to 32-bit std::popcount");
    return std::popcount(x);
  }
  static uint32_t FirstBit(SetType x) {
    MOZ_ASSERT(x);
    return std::countr_zero(x);
  }
  static uint32_t LastBit(SetType x) {
    MOZ_ASSERT(x);
    return std::bit_width(x) - 1;
  }

 private:
  // Note: These fields are using one extra bit to make the invalid enumerated
  // values fit, and thus prevent a warning.
  Codes::Encoding reg_ : 5;
  Codes::ContentType type_ : 2;
  bool isInvalid_ : 1;

  // Constants used for exporting/importing the float register code.
#if defined(JS_CODEGEN_X86)
  static const size_t RegSize = 3;
#elif defined(JS_CODEGEN_X64)
  static const size_t RegSize = 4;
#endif
  static const size_t RegMask = (1 << RegSize) - 1;

 public:
  constexpr FloatRegister()
      : reg_(Codes::Encoding(0)), type_(Codes::Single), isInvalid_(true) {}
  constexpr FloatRegister(uint32_t r, Codes::ContentType k)
      : reg_(Codes::Encoding(r)), type_(k), isInvalid_(false) {}
  constexpr FloatRegister(Codes::Encoding r, Codes::ContentType k)
      : reg_(r), type_(k), isInvalid_(false) {}

  static FloatRegister FromCode(uint32_t i) {
    MOZ_ASSERT(i < Codes::Total);
    return FloatRegister(i & RegMask, Codes::ContentType(i >> RegSize));
  }

  bool isSingle() const {
    MOZ_ASSERT(!isInvalid());
    return type_ == Codes::Single;
  }
  bool isDouble() const {
    MOZ_ASSERT(!isInvalid());
    return type_ == Codes::Double;
  }
  bool isSimd128() const {
    MOZ_ASSERT(!isInvalid());
    return type_ == Codes::Simd128;
  }
  bool isInvalid() const { return isInvalid_; }

  FloatRegister asSingle() const {
    MOZ_ASSERT(!isInvalid());
    return FloatRegister(reg_, Codes::Single);
  }
  FloatRegister asDouble() const {
    MOZ_ASSERT(!isInvalid());
    return FloatRegister(reg_, Codes::Double);
  }
  FloatRegister asSimd128() const {
    MOZ_ASSERT(!isInvalid());
    return FloatRegister(reg_, Codes::Simd128);
  }

  uint32_t size() const {
    MOZ_ASSERT(!isInvalid());
    if (isSingle()) {
      return sizeof(float);
    }
    if (isDouble()) {
      return sizeof(double);
    }
    MOZ_ASSERT(isSimd128());
    return 4 * sizeof(int32_t);
  }

  Code code() const {
    MOZ_ASSERT(!isInvalid());
    MOZ_ASSERT(uint32_t(reg_) < Codes::TotalPhys);
    // :TODO: ARM is doing the same thing, but we should avoid this, except
    // that the RegisterSets depends on this.
    return Code(reg_ | (type_ << RegSize));
  }
  Encoding encoding() const {
    MOZ_ASSERT(!isInvalid());
    MOZ_ASSERT(uint32_t(reg_) < Codes::TotalPhys);
    return reg_;
  }
  // defined in Assembler-x86-shared.cpp
  const char* name() const;
  bool volatile_() const {
    return !!((SetType(1) << code()) & FloatRegisters::VolatileMask);
  }
  bool operator!=(FloatRegister other) const {
    return other.reg_ != reg_ || other.type_ != type_;
  }
  bool operator==(FloatRegister other) const {
    return other.reg_ == reg_ && other.type_ == type_;
  }
  bool aliases(FloatRegister other) const { return other.reg_ == reg_; }
  // Check if two floating point registers have the same type.
  bool equiv(FloatRegister other) const { return other.type_ == type_; }

  uint32_t numAliased() const { return Codes::NumTypes; }
  uint32_t numAlignedAliased() const { return numAliased(); }

  FloatRegister aliased(uint32_t aliasIdx) const {
    MOZ_ASSERT(aliasIdx < Codes::NumTypes);
    return FloatRegister(
        reg_, Codes::ContentType((aliasIdx + type_) % Codes::NumTypes));
  }
  FloatRegister alignedAliased(uint32_t aliasIdx) const {
    return aliased(aliasIdx);
  }

  SetType alignedOrDominatedAliasedSet() const { return Codes::Spread << reg_; }

  static constexpr RegTypeName DefaultType = RegTypeName::Float64;

  template <RegTypeName = DefaultType>
  static SetType LiveAsIndexableSet(SetType s) {
    return SetType(0);
  }

  template <RegTypeName Name = DefaultType>
  static SetType AllocatableAsIndexableSet(SetType s) {
    static_assert(Name != RegTypeName::Any, "Allocatable set are not iterable");
    return LiveAsIndexableSet<Name>(s);
  }

  static TypedRegisterSet<FloatRegister> ReduceSetForPush(
      const TypedRegisterSet<FloatRegister>& s);
  static uint32_t GetPushSizeInBytes(const TypedRegisterSet<FloatRegister>& s);
  uint32_t getRegisterDumpOffsetInBytes();
};

template <>
inline FloatRegister::SetType
FloatRegister::LiveAsIndexableSet<RegTypeName::Float32>(SetType set) {
  return set & FloatRegisters::AllSingleMask;
}

template <>
inline FloatRegister::SetType
FloatRegister::LiveAsIndexableSet<RegTypeName::Float64>(SetType set) {
  return set & FloatRegisters::AllDoubleMask;
}

template <>
inline FloatRegister::SetType
FloatRegister::LiveAsIndexableSet<RegTypeName::Vector128>(SetType set) {
  return set & FloatRegisters::AllVector128Mask;
}

template <>
inline FloatRegister::SetType
FloatRegister::LiveAsIndexableSet<RegTypeName::Any>(SetType set) {
  return set;
}

// On ARM, Dn aliases both S2n and S2n+1, so if you need to convert a float32
// to a double as a temporary, you need a temporary double register.
inline bool hasMultiAlias() { return false; }

}  // namespace jit
}  // namespace js

#endif /* jit_x86_shared_Architecture_x86_h */
