/* 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_arm64_Architecture_arm64_h
#define jit_arm64_Architecture_arm64_h

#include "mozilla/Assertions.h"

#include <algorithm>
#include <bit>

#include "jit/arm64/vixl/Cpu-Features-vixl.h"
#include "jit/arm64/vixl/Instructions-vixl.h"
#include "jit/shared/Architecture-shared.h"

#include "js/Utility.h"

#define JS_HAS_HIDDEN_SP
static const uint32_t HiddenSPEncoding = vixl::kSPRegInternalCode;

namespace js {
namespace jit {

// AArch64 has 32 64-bit integer registers, x0 though x31.
//
//  x31 (or, more accurately, the integer register with encoding 31, since
//  there is no x31 per se) is special and functions as both the stack pointer
//  and a zero register.
//
//  The bottom 32 bits of each of the X registers is accessible as w0 through
//  w31. The program counter is not accessible as a register.
//
// SIMD and scalar floating-point registers share a register bank.
//  32 bit float registers are s0 through s31.
//  64 bit double registers are d0 through d31.
//  128 bit SIMD registers are v0 through v31.
//  e.g., s0 is the bottom 32 bits of d0, which is the bottom 64 bits of v0.

// AArch64 Calling Convention:
//  x0 - x7: arguments and return value
//  x8: indirect result (struct) location
//  x9 - x15: temporary registers
//  x16 - x17: intra-call-use registers (PLT, linker)
//  x18: platform specific use (TLS)
//  x19 - x28: callee-saved registers
//  x29: frame pointer
//  x30: link register

// AArch64 Calling Convention for Floats:
//  d0 - d7: arguments and return value
//  d8 - d15: callee-saved registers
//   Bits 64:128 are not saved for v8-v15.
//  d16 - d31: temporary registers

// AArch64 does not have soft float.

class Registers {
 public:
  enum RegisterID {
    w0 = 0,
    x0 = 0,
    w1 = 1,
    x1 = 1,
    w2 = 2,
    x2 = 2,
    w3 = 3,
    x3 = 3,
    w4 = 4,
    x4 = 4,
    w5 = 5,
    x5 = 5,
    w6 = 6,
    x6 = 6,
    w7 = 7,
    x7 = 7,
    w8 = 8,
    x8 = 8,
    w9 = 9,
    x9 = 9,
    w10 = 10,
    x10 = 10,
    w11 = 11,
    x11 = 11,
    w12 = 12,
    x12 = 12,
    w13 = 13,
    x13 = 13,
    w14 = 14,
    x14 = 14,
    w15 = 15,
    x15 = 15,
    w16 = 16,
    x16 = 16,
    ip0 = 16,  // MacroAssembler scratch register 1.
    w17 = 17,
    x17 = 17,
    ip1 = 17,  // MacroAssembler scratch register 2.
    w18 = 18,
    x18 = 18,
    tls = 18,  // Platform-specific use (TLS).
    w19 = 19,
    x19 = 19,
    w20 = 20,
    x20 = 20,
    w21 = 21,
    x21 = 21,
    w22 = 22,
    x22 = 22,
    w23 = 23,
    x23 = 23,
    w24 = 24,
    x24 = 24,
    w25 = 25,
    x25 = 25,
    w26 = 26,
    x26 = 26,
    w27 = 27,
    x27 = 27,
    w28 = 28,
    x28 = 28,
    w29 = 29,
    x29 = 29,
    fp = 29,
    w30 = 30,
    x30 = 30,
    lr = 30,
    w31 = 31,
    x31 = 31,
    wzr = 31,
    xzr = 31,
    sp = 31,  // Special: both stack pointer and a zero register.
  };
  using Code = uint8_t;
  using Encoding = uint32_t;
  using SetType = uint32_t;

  static const Code Invalid = 0xFF;

  union RegisterContent {
    uintptr_t r;
  };

  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 const char* GetName(uint32_t code) {
    static const char* const Names[] = {
        "x0",  "x1",  "x2",  "x3",  "x4",  "x5",  "x6",  "x7",
        "x8",  "x9",  "x10", "x11", "x12", "x13", "x14", "x15",
        "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
        "x24", "x25", "x26", "x27", "x28", "x29", "lr",  "sp"};
    static_assert(Total == std::size(Names), "Table is the correct size");
    if (code >= Total) {
      return "invalid";
    }
    return Names[code];
  }

  static Code FromName(const char* name);

  static const uint32_t Total = 32;
  static const uint32_t TotalPhys = 32;
  static const uint32_t Allocatable =
      27;  // No named special-function registers.

  static const SetType AllMask = 0xFFFFFFFF;
  static const SetType NoneMask = 0x0;

  static const SetType ArgRegMask =
      (1 << Registers::x0) | (1 << Registers::x1) | (1 << Registers::x2) |
      (1 << Registers::x3) | (1 << Registers::x4) | (1 << Registers::x5) |
      (1 << Registers::x6) | (1 << Registers::x7);

  static const SetType VolatileMask =
      (1 << Registers::x0) | (1 << Registers::x1) | (1 << Registers::x2) |
      (1 << Registers::x3) | (1 << Registers::x4) | (1 << Registers::x5) |
      (1 << Registers::x6) | (1 << Registers::x7) | (1 << Registers::x8) |
      (1 << Registers::x9) | (1 << Registers::x10) | (1 << Registers::x11) |
      (1 << Registers::x12) | (1 << Registers::x13) | (1 << Registers::x14) |
      (1 << Registers::x15) | (1 << Registers::x16) | (1 << Registers::x17) |
      (1 << Registers::x18);

  static const SetType NonVolatileMask =
      (1 << Registers::x19) | (1 << Registers::x20) | (1 << Registers::x21) |
      (1 << Registers::x22) | (1 << Registers::x23) | (1 << Registers::x24) |
      (1 << Registers::x25) | (1 << Registers::x26) | (1 << Registers::x27) |
      (1 << Registers::x28) | (1 << Registers::x29) | (1 << Registers::x30);

  static const SetType NonAllocatableMask =
      (1 << Registers::x20) |  // PseudoStackPointer.
      (1 << Registers::ip0) |  // First scratch register.
      (1 << Registers::ip1) |  // Second scratch register.
      (1 << Registers::tls) | (1 << Registers::lr) | (1 << Registers::sp) |
      (1 << Registers::fp);

  static const SetType WrapperMask = VolatileMask;

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

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

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

// Smallest integer type that can hold a register bitmask.
using PackedRegisterMask = uint32_t;

template <typename T>
class TypedRegisterSet;

// 128-bit bitset for FloatRegisters::SetType.

class Bitset128 {
  // The order (hi, lo) looks best in the debugger.
  uint64_t hi, lo;

 public:
  MOZ_IMPLICIT constexpr Bitset128(uint64_t initial) : hi(0), lo(initial) {}
  MOZ_IMPLICIT constexpr Bitset128(const Bitset128& that)
      : hi(that.hi), lo(that.lo) {}

  constexpr Bitset128(uint64_t hi, uint64_t lo) : hi(hi), lo(lo) {}

  constexpr uint64_t high() const { return hi; }

  constexpr uint64_t low() const { return lo; }

  constexpr Bitset128 operator|(Bitset128 that) const {
    return Bitset128(hi | that.hi, lo | that.lo);
  }

  constexpr Bitset128 operator&(Bitset128 that) const {
    return Bitset128(hi & that.hi, lo & that.lo);
  }

  constexpr Bitset128 operator^(Bitset128 that) const {
    return Bitset128(hi ^ that.hi, lo ^ that.lo);
  }

  constexpr Bitset128 operator~() const { return Bitset128(~hi, ~lo); }

  // We must avoid shifting by the word width, which is complex.  Inlining plus
  // shift-by-constant will remove a lot of code in the normal case.

  constexpr Bitset128 operator<<(size_t shift) const {
    if (shift == 0) {
      return *this;
    }
    if (shift < 64) {
      return Bitset128((hi << shift) | (lo >> (64 - shift)), lo << shift);
    }
    if (shift == 64) {
      return Bitset128(lo, 0);
    }
    return Bitset128(lo << (shift - 64), 0);
  }

  constexpr Bitset128 operator>>(size_t shift) const {
    if (shift == 0) {
      return *this;
    }
    if (shift < 64) {
      return Bitset128(hi >> shift, (lo >> shift) | (hi << (64 - shift)));
    }
    if (shift == 64) {
      return Bitset128(0, hi);
    }
    return Bitset128(0, hi >> (shift - 64));
  }

  constexpr bool operator==(Bitset128 that) const {
    return lo == that.lo && hi == that.hi;
  }

  constexpr bool operator!=(Bitset128 that) const {
    return lo != that.lo || hi != that.hi;
  }

  constexpr bool operator!() const { return (hi | lo) == 0; }

  Bitset128& operator|=(const Bitset128& that) {
    hi |= that.hi;
    lo |= that.lo;
    return *this;
  }

  Bitset128& operator&=(const Bitset128& that) {
    hi &= that.hi;
    lo &= that.lo;
    return *this;
  }

  uint32_t size() const { return std::popcount(hi) + std::popcount(lo); }

  uint32_t countTrailingZeroes() const {
    if (lo) {
      return std::countr_zero(lo);
    }
    return std::countr_zero(hi) + 64;
  }

  uint32_t countLeadingZeroes() const {
    if (hi) {
      return std::countl_zero(hi);
    }
    return std::countl_zero(lo) + 64;
  }

  uint32_t bitWidth() const { return 128 - countLeadingZeroes(); }
};

class FloatRegisters {
 public:
  enum FPRegisterID {
    s0 = 0,
    d0 = 0,
    v0 = 0,
    s1 = 1,
    d1 = 1,
    v1 = 1,
    s2 = 2,
    d2 = 2,
    v2 = 2,
    s3 = 3,
    d3 = 3,
    v3 = 3,
    s4 = 4,
    d4 = 4,
    v4 = 4,
    s5 = 5,
    d5 = 5,
    v5 = 5,
    s6 = 6,
    d6 = 6,
    v6 = 6,
    s7 = 7,
    d7 = 7,
    v7 = 7,
    s8 = 8,
    d8 = 8,
    v8 = 8,
    s9 = 9,
    d9 = 9,
    v9 = 9,
    s10 = 10,
    d10 = 10,
    v10 = 10,
    s11 = 11,
    d11 = 11,
    v11 = 11,
    s12 = 12,
    d12 = 12,
    v12 = 12,
    s13 = 13,
    d13 = 13,
    v13 = 13,
    s14 = 14,
    d14 = 14,
    v14 = 14,
    s15 = 15,
    d15 = 15,
    v15 = 15,
    s16 = 16,
    d16 = 16,
    v16 = 16,
    s17 = 17,
    d17 = 17,
    v17 = 17,
    s18 = 18,
    d18 = 18,
    v18 = 18,
    s19 = 19,
    d19 = 19,
    v19 = 19,
    s20 = 20,
    d20 = 20,
    v20 = 20,
    s21 = 21,
    d21 = 21,
    v21 = 21,
    s22 = 22,
    d22 = 22,
    v22 = 22,
    s23 = 23,
    d23 = 23,
    v23 = 23,
    s24 = 24,
    d24 = 24,
    v24 = 24,
    s25 = 25,
    d25 = 25,
    v25 = 25,
    s26 = 26,
    d26 = 26,
    v26 = 26,
    s27 = 27,
    d27 = 27,
    v27 = 27,
    s28 = 28,
    d28 = 28,
    v28 = 28,
    s29 = 29,
    d29 = 29,
    v29 = 29,
    s30 = 30,
    d30 = 30,
    v30 = 30,
    s31 = 31,
    d31 = 31,
    v31 = 31,  // Scratch register.
  };

  // Eight bits: (invalid << 7) | (kind << 5) | encoding
  using Code = uint8_t;
  using Encoding = FPRegisterID;
  using SetType = Bitset128;

  enum Kind : uint8_t { Single, Double, Simd128, NumTypes };

  static constexpr Code Invalid = 0x80;

  static const char* GetName(uint32_t code) {
    // clang-format off
    static const char* const Names[] = {
        "s0",  "s1",  "s2",  "s3",  "s4",  "s5",  "s6",  "s7",  "s8",  "s9",
        "s10", "s11", "s12", "s13", "s14", "s15", "s16", "s17", "s18", "s19",
        "s20", "s21", "s22", "s23", "s24", "s25", "s26", "s27", "s28", "s29",
        "s30", "s31",

        "d0",  "d1",  "d2",  "d3",  "d4",  "d5",  "d6",  "d7",  "d8",  "d9",
        "d10", "d11", "d12", "d13", "d14", "d15", "d16", "d17", "d18", "d19",
        "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29",
        "d30", "d31",

        "v0",  "v1",  "v2",  "v3",  "v4",  "v5",  "v6",  "v7",  "v8",  "v9",
        "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19",
        "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29",
        "v30", "v31",
    };
    // clang-format on
    static_assert(Total == std::size(Names), "Table is the correct size");
    if (code >= Total) {
      return "invalid";
    }
    return Names[code];
  }

  static Code FromName(const char* name);

  static const uint32_t TotalPhys = 32;
  static const uint32_t Total = TotalPhys * NumTypes;
  static const uint32_t Allocatable = 31;  // Without d31, the scratch register.

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

  static constexpr unsigned ShiftSingle = uint32_t(Single) * TotalPhys;
  static constexpr unsigned ShiftDouble = uint32_t(Double) * TotalPhys;
  static constexpr unsigned ShiftSimd128 = uint32_t(Simd128) * TotalPhys;

  static constexpr SetType NoneMask = SetType(0);
  static constexpr SetType AllPhysMask = ~(~SetType(0) << TotalPhys);
  static constexpr SetType AllSingleMask = AllPhysMask << ShiftSingle;
  static constexpr SetType AllDoubleMask = AllPhysMask << ShiftDouble;
  static constexpr SetType AllSimd128Mask = AllPhysMask << ShiftSimd128;
  static constexpr SetType AllMask =
      AllDoubleMask | AllSingleMask | AllSimd128Mask;
  static constexpr SetType AliasMask = (SetType(1) << ShiftSingle) |
                                       (SetType(1) << ShiftDouble) |
                                       (SetType(1) << ShiftSimd128);

  static_assert(ShiftSingle == 0,
                "Or the NonVolatileMask must be computed differently");

  static constexpr SetType NonVolatileSingleMask =
      SetType((1 << FloatRegisters::s8) | (1 << FloatRegisters::s9) |
              (1 << FloatRegisters::s10) | (1 << FloatRegisters::s11) |
              (1 << FloatRegisters::s12) | (1 << FloatRegisters::s13) |
              (1 << FloatRegisters::s14) | (1 << FloatRegisters::s15));

  // Note: only the bottom 64 bits of v8-v15 will be preserved.
  static constexpr SetType NonVolatileMask =
      (NonVolatileSingleMask << ShiftSingle) |
      (NonVolatileSingleMask << ShiftDouble);

  static constexpr SetType VolatileMask = AllMask & ~NonVolatileMask;

  static constexpr SetType WrapperMask = VolatileMask;

  static_assert(ShiftSingle == 0,
                "Or the NonAllocatableMask must be computed differently");

  // d31 is the ScratchFloatReg.
  static constexpr SetType NonAllocatableSingleMask =
      (SetType(1) << FloatRegisters::s31);

  static constexpr SetType NonAllocatableMask =
      NonAllocatableSingleMask | (NonAllocatableSingleMask << ShiftDouble) |
      (NonAllocatableSingleMask << ShiftSimd128);

  static constexpr SetType AllocatableMask = AllMask & ~NonAllocatableMask;

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

  static constexpr Encoding encoding(Code c) {
    MOZ_ASSERT(c < Total);
    return Encoding(c & 31);
  }

  static constexpr Kind kind(Code c) {
    MOZ_ASSERT(c < Total && ((c >> 5) & 3) < NumTypes);
    return Kind((c >> 5) & 3);
  }

  static constexpr Code fromParts(uint32_t encoding, uint32_t kind,
                                  uint32_t invalid) {
    return Code((invalid << 7) | (kind << 5) | encoding);
  }
};

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

static constexpr uint32_t ShadowStackSpace = 0;

// When our only strategy for far jumps is to encode the offset directly, and
// not insert any jump islands during assembly for even further jumps, then the
// architecture restricts us to -2^27 .. 2^27-4, to fit into a signed 28-bit
// value.  We further reduce this range to allow the far-jump inserting code to
// have some breathing room.
static const uint32_t JumpImmediateRange = ((1 << 27) - (20 * 1024 * 1024));

static const uint32_t ABIStackAlignment = 16;
static const uint32_t CodeAlignment = 16;
static const bool StackKeptAligned = false;

// Although sp is only usable if 16-byte alignment is kept,
// the Pseudo-StackPointer enables use of 8-byte alignment.
static const uint32_t StackAlignment = 8;
static const uint32_t NativeFrameSize = 8;

struct FloatRegister {
  using Codes = FloatRegisters;
  using Code = Codes::Code;
  using Encoding = Codes::Encoding;
  using SetType = Codes::SetType;

  static uint32_t SetSize(SetType x) {
    x |= x >> FloatRegisters::TotalPhys;
    x |= x >> FloatRegisters::TotalPhys;
    x &= FloatRegisters::AllPhysMask;
    MOZ_ASSERT(x.high() == 0);
    MOZ_ASSERT((x.low() >> 32) == 0);
    return std::popcount(x.low());
  }

  static uint32_t FirstBit(SetType x) {
    MOZ_ASSERT(x);
    return x.countTrailingZeroes();
  }
  static uint32_t LastBit(SetType x) {
    MOZ_ASSERT(x);
    return x.bitWidth() - 1;
  }

  static constexpr size_t SizeOfSimd128 = 16;

 private:
  // These fields only hold valid values: an invalid register is always
  // represented as a valid encoding and kind with the invalid_ bit set.
  uint8_t encoding_;  // 32 encodings
  uint8_t kind_;      // Double, Single, Simd128
  bool invalid_;

  using Kind = Codes::Kind;

 public:
  constexpr FloatRegister(Encoding encoding, Kind kind)
      : encoding_(encoding), kind_(kind), invalid_(false) {
    MOZ_ASSERT(uint32_t(encoding) < Codes::TotalPhys);
  }

  constexpr FloatRegister()
      : encoding_(0), kind_(FloatRegisters::Double), invalid_(true) {}

  static FloatRegister FromCode(uint32_t i) {
    MOZ_ASSERT(i < Codes::Total);
    return FloatRegister(FloatRegisters::encoding(i), FloatRegisters::kind(i));
  }

  bool isSingle() const {
    MOZ_ASSERT(!invalid_);
    return kind_ == FloatRegisters::Single;
  }
  bool isDouble() const {
    MOZ_ASSERT(!invalid_);
    return kind_ == FloatRegisters::Double;
  }
  bool isSimd128() const {
    MOZ_ASSERT(!invalid_);
    return kind_ == FloatRegisters::Simd128;
  }
  bool isInvalid() const { return invalid_; }

  FloatRegister asSingle() const {
    MOZ_ASSERT(!invalid_);
    return FloatRegister(Encoding(encoding_), FloatRegisters::Single);
  }
  FloatRegister asDouble() const {
    MOZ_ASSERT(!invalid_);
    return FloatRegister(Encoding(encoding_), FloatRegisters::Double);
  }
  FloatRegister asSimd128() const {
    MOZ_ASSERT(!invalid_);
    return FloatRegister(Encoding(encoding_), FloatRegisters::Simd128);
  }

  constexpr uint32_t size() const {
    MOZ_ASSERT(!invalid_);
    if (kind_ == FloatRegisters::Double) {
      return sizeof(double);
    }
    if (kind_ == FloatRegisters::Single) {
      return sizeof(float);
    }
    MOZ_ASSERT(kind_ == FloatRegisters::Simd128);
    return SizeOfSimd128;
  }

  constexpr Code code() const {
    MOZ_ASSERT(!invalid_);
    return Codes::fromParts(encoding_, kind_, invalid_);
  }

  constexpr Encoding encoding() const {
    MOZ_ASSERT(!invalid_);
    return Encoding(encoding_);
  }

  const char* name() const { return FloatRegisters::GetName(code()); }
  bool volatile_() const {
    MOZ_ASSERT(!invalid_);
    return !!((SetType(1) << code()) & FloatRegisters::VolatileMask);
  }
  constexpr bool operator==(const FloatRegister&) const = default;

  bool aliases(FloatRegister other) const {
    return other.encoding_ == encoding_;
  }
  // This function mostly exists for the ARM backend.  It is to ensure that two
  // floating point registers' types are equivalent.  e.g. S0 is not equivalent
  // to D16, since S0 holds a float32, and D16 holds a Double.
  // Since all floating point registers on x86 and x64 are equivalent, it is
  // reasonable for this function to do the same.
  bool equiv(FloatRegister other) const {
    MOZ_ASSERT(!invalid_);
    return kind_ == other.kind_;
  }

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

  FloatRegister aliased(uint32_t aliasIdx) {
    MOZ_ASSERT(!invalid_);
    MOZ_ASSERT(aliasIdx < numAliased());
    return FloatRegister(Encoding(encoding_),
                         Kind((aliasIdx + kind_) % Codes::NumTypes));
  }
  FloatRegister alignedAliased(uint32_t aliasIdx) { return aliased(aliasIdx); }
  SetType alignedOrDominatedAliasedSet() const {
    return Codes::AliasMask << encoding_;
  }

  static constexpr RegTypeName DefaultType = RegTypeName::Float64;

  template <RegTypeName Name = 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();

  // For N in 0..31, if any of sN, dN or qN is a member of `s`, the
  // returned set will contain all of sN, dN and qN.
  static TypedRegisterSet<FloatRegister> BroadcastToAllSizes(
      const TypedRegisterSet<FloatRegister>& s);
};

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::AllSimd128Mask;
}

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

class ARM64Flags final {
  // List of enabled CPU features. Initialized once |Init()|.
  static inline vixl::CPUFeatures features{};

  // List of disabled CPU features. Must be set before calling |Init()|.
  static inline vixl::CPUFeatures disabledFeatures{};

 public:
  ARM64Flags() = delete;

  // ARM64Flags::Init is called from the JitContext constructor to read the
  // hardware flags. This method must only be called exactly once.
  static void Init();

  static bool IsInitialized() { return features != vixl::CPUFeatures::None(); }

  static vixl::CPUFeatures GetCPUFeatures() {
    MOZ_ASSERT(IsInitialized());
    return features;
  }

  // Disable CPU features for testing. Can be called repeatedly to disable
  // additional features. Must be called before |Init()|.
  static void DisableCPUFeatures(vixl::CPUFeatures features) {
    MOZ_ASSERT(!IsInitialized());
    disabledFeatures.Combine(features);
  }

  static uint32_t GetFlags() {
    MOZ_ASSERT(IsInitialized());

    // TODO: vixl::CPUFeatures supports more than 32 values, so it can't be used
    // directly for GetFlags.
    return 0;
  }
};

// ARM prior to ARMv8 also has doubles that alias multiple floats.
// Again, ARMv8 is in the clear.
inline bool hasMultiAlias() { return false; }

// Retrieve the ARM64 hardware flags as a bitmask. They must have been set.
inline uint32_t GetARM64Flags() { return ARM64Flags::GetFlags(); }

bool CanFlushICacheFromBackgroundThreads();

}  // namespace jit
}  // namespace js

#endif  // jit_arm64_Architecture_arm64_h
