/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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_RegisterSets_h #define jit_RegisterSets_h #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Variant.h" #include #include #include #include "jit/IonTypes.h" #include "jit/Registers.h" #include "js/Value.h" namespace js { namespace jit { struct AnyRegister { using Code = uint8_t; static const uint8_t Total = Registers::Total + FloatRegisters::Total; static const uint8_t FirstFloatReg = Registers::Total; static const uint8_t Invalid = UINT8_MAX; static_assert(size_t(Registers::Total) + FloatRegisters::Total <= UINT8_MAX, "Number of registers must fit in uint8_t"); private: Code code_; public: AnyRegister() : code_(Invalid) {} explicit AnyRegister(Register gpr) { code_ = gpr.code(); } explicit AnyRegister(FloatRegister fpu) { code_ = fpu.code() + Registers::Total; } static AnyRegister FromCode(uint8_t i) { MOZ_ASSERT(i < Total); AnyRegister r; r.code_ = i; return r; } bool isFloat() const { MOZ_ASSERT(isValid()); return code_ >= Registers::Total; } Register gpr() const { MOZ_ASSERT(!isFloat()); return Register::FromCode(code_); } FloatRegister fpu() const { MOZ_ASSERT(isFloat()); return FloatRegister::FromCode(code_ - Registers::Total); } bool operator==(AnyRegister other) const { // We don't need the operands to be valid to test for equality. return code_ == other.code_; } bool operator!=(AnyRegister other) const { // We don't need the operands to be valid to test for equality. return code_ != other.code_; } const char* name() const { return isFloat() ? fpu().name() : gpr().name(); } Code code() const { MOZ_ASSERT(isValid()); return code_; } bool volatile_() const { return isFloat() ? fpu().volatile_() : gpr().volatile_(); } AnyRegister aliased(uint8_t aliasIdx) const { AnyRegister ret; if (isFloat()) { ret = AnyRegister(fpu().aliased(aliasIdx)); } else { ret = AnyRegister(gpr().aliased(aliasIdx)); } MOZ_ASSERT_IF(aliasIdx == 0, ret == *this); return ret; } uint8_t numAliased() const { if (isFloat()) { return fpu().numAliased(); } return gpr().numAliased(); } bool aliases(const AnyRegister& other) const { if (isFloat() && other.isFloat()) { return fpu().aliases(other.fpu()); } if (!isFloat() && !other.isFloat()) { return gpr().aliases(other.gpr()); } return false; } // do the two registers hold the same type of data (e.g. both float32, both // gpr) bool isCompatibleReg(const AnyRegister other) const { if (isFloat() && other.isFloat()) { return fpu().equiv(other.fpu()); } if (!isFloat() && !other.isFloat()) { return true; } return false; } bool isValid() const { return code_ != Invalid; } }; // Registers to hold a boxed value. Uses one register on 64 bit // platforms, two registers on 32 bit platforms. class ValueOperand { #if defined(JS_NUNBOX32) Register type_; Register payload_; public: constexpr ValueOperand(Register type, Register payload) : type_(type), payload_(payload) {} constexpr Register typeReg() const { return type_; } constexpr Register payloadReg() const { return payload_; } constexpr Register64 toRegister64() const { return Register64(typeReg(), payloadReg()); } constexpr bool aliases(Register reg) const { return type_ == reg || payload_ == reg; } constexpr Register payloadOrValueReg() const { return payloadReg(); } bool hasVolatileReg() const { return type_.volatile_() || payload_.volatile_(); } constexpr bool operator==(const ValueOperand& o) const { return type_ == o.type_ && payload_ == o.payload_; } constexpr bool operator!=(const ValueOperand& o) const { return !(*this == o); } #elif defined(JS_PUNBOX64) Register value_; public: explicit constexpr ValueOperand(Register value) : value_(value) {} constexpr Register valueReg() const { return value_; } constexpr Register64 toRegister64() const { return Register64(valueReg()); } constexpr bool aliases(Register reg) const { return value_ == reg; } constexpr Register payloadOrValueReg() const { return valueReg(); } bool hasVolatileReg() const { return value_.volatile_(); } constexpr bool operator==(const ValueOperand& o) const { return value_ == o.value_; } constexpr bool operator!=(const ValueOperand& o) const { return !(*this == o); } #endif constexpr Register scratchReg() const { return payloadOrValueReg(); } ValueOperand() = default; }; // Registers to hold either either a typed or untyped value. class TypedOrValueRegister { // Type of value being stored. MIRType type_; union U { AnyRegister::Code typed; #if defined(JS_PUNBOX64) Register::Code value; #elif defined(JS_NUNBOX32) struct { Register::Code valueType; Register::Code valuePayload; } s; #else # error "Bad architecture" #endif } data; public: TypedOrValueRegister() = default; TypedOrValueRegister(MIRType type, AnyRegister reg) : type_(type) { data.typed = reg.code(); } MOZ_IMPLICIT TypedOrValueRegister(ValueOperand value) : type_(MIRType::Value) { #if defined(JS_PUNBOX64) data.value = value.valueReg().code(); #elif defined(JS_NUNBOX32) data.s.valueType = value.typeReg().code(); data.s.valuePayload = value.payloadReg().code(); #else # error "Bad architecture" #endif } MIRType type() const { return type_; } bool hasTyped() const { return type() != MIRType::None && type() != MIRType::Value; } bool hasValue() const { return type() == MIRType::Value; } AnyRegister typedReg() const { MOZ_ASSERT(hasTyped()); return AnyRegister::FromCode(data.typed); } ValueOperand valueReg() const { MOZ_ASSERT(hasValue()); #if defined(JS_PUNBOX64) return ValueOperand(Register::FromCode(data.value)); #elif defined(JS_NUNBOX32) return ValueOperand(Register::FromCode(data.s.valueType), Register::FromCode(data.s.valuePayload)); #else # error "Bad architecture" #endif } AnyRegister scratchReg() { if (hasValue()) { return AnyRegister(valueReg().scratchReg()); } return typedReg(); } }; // A constant value, or registers to hold a typed/untyped value. class ConstantOrRegister { // Whether a constant value is being stored. bool constant_; // Space to hold either a Value or a TypedOrValueRegister. union U { JS::Value constant; TypedOrValueRegister reg; // |constant| has a non-trivial constructor and therefore MUST be // placement-new'd into existence. MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS U() {} MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS } data; public: ConstantOrRegister() = delete; MOZ_IMPLICIT ConstantOrRegister(const JS::Value& value) : constant_(true) { MOZ_ASSERT(constant()); new (&data.constant) JS::Value(value); } MOZ_IMPLICIT ConstantOrRegister(TypedOrValueRegister reg) : constant_(false) { MOZ_ASSERT(!constant()); new (&data.reg) TypedOrValueRegister(reg); } bool constant() const { return constant_; } JS::Value value() const { MOZ_ASSERT(constant()); return data.constant; } const TypedOrValueRegister& reg() const { MOZ_ASSERT(!constant()); return data.reg; } }; template class TypedRegisterSet { public: using RegType = T; using SetType = typename T::SetType; private: SetType bits_; public: explicit constexpr TypedRegisterSet(SetType bits) : bits_(bits) {} constexpr TypedRegisterSet() : bits_(0) {} constexpr TypedRegisterSet(const TypedRegisterSet& set) : bits_(set.bits_) {} static inline TypedRegisterSet All() { return TypedRegisterSet(T::Codes::AllocatableMask); } static inline TypedRegisterSet Intersect(const TypedRegisterSet& lhs, const TypedRegisterSet& rhs) { return TypedRegisterSet(lhs.bits_ & rhs.bits_); } static inline TypedRegisterSet Union(const TypedRegisterSet& lhs, const TypedRegisterSet& rhs) { return TypedRegisterSet(lhs.bits_ | rhs.bits_); } static inline TypedRegisterSet Not(const TypedRegisterSet& in) { return TypedRegisterSet(~in.bits_ & T::Codes::AllocatableMask); } static inline TypedRegisterSet Subtract(const TypedRegisterSet& lhs, const TypedRegisterSet& rhs) { return TypedRegisterSet(lhs.bits_ & ~rhs.bits_); } static inline TypedRegisterSet VolatileNot(const TypedRegisterSet& in) { const SetType allocatableVolatile = T::Codes::AllocatableMask & T::Codes::VolatileMask; return TypedRegisterSet(~in.bits_ & allocatableVolatile); } static inline TypedRegisterSet Volatile() { return TypedRegisterSet(T::Codes::AllocatableMask & T::Codes::VolatileMask); } static inline TypedRegisterSet NonVolatile() { return TypedRegisterSet(T::Codes::AllocatableMask & T::Codes::NonVolatileMask); } bool empty() const { return !bits_; } void clear() { bits_ = 0; } bool hasRegisterIndex(T reg) const { return !!(bits_ & (SetType(1) << reg.code())); } bool hasAllocatable(T reg) const { return !(~bits_ & reg.alignedOrDominatedAliasedSet()); } void addRegisterIndex(T reg) { bits_ |= (SetType(1) << reg.code()); } void addAllocatable(T reg) { bits_ |= reg.alignedOrDominatedAliasedSet(); } void takeRegisterIndex(T reg) { bits_ &= ~(SetType(1) << reg.code()); } void takeAllocatable(T reg) { bits_ &= ~reg.alignedOrDominatedAliasedSet(); } static constexpr RegTypeName DefaultType = RegType::DefaultType; template SetType allLive() const { return T::template LiveAsIndexableSet(bits_); } template SetType allAllocatable() const { return T::template AllocatableAsIndexableSet(bits_); } static RegType FirstRegister(SetType set) { return RegType::FromCode(RegType::FirstBit(set)); } static RegType LastRegister(SetType set) { return RegType::FromCode(RegType::LastBit(set)); } SetType bits() const { return bits_; } uint32_t size() const { return T::SetSize(bits_); } bool operator==(const TypedRegisterSet& other) const { return other.bits_ == bits_; } TypedRegisterSet reduceSetForPush() const { return T::ReduceSetForPush(*this); } uint32_t getPushSizeInBytes() const { return T::GetPushSizeInBytes(*this); } size_t offsetOfPushedRegister(RegType reg) const { MOZ_ASSERT(hasRegisterIndex(reg)); return T::OffsetOfPushedRegister(bits(), reg); } }; using GeneralRegisterSet = TypedRegisterSet; using FloatRegisterSet = TypedRegisterSet; class AnyRegisterIterator; class RegisterSet { GeneralRegisterSet gpr_; FloatRegisterSet fpu_; friend class AnyRegisterIterator; public: RegisterSet() = default; constexpr RegisterSet(const GeneralRegisterSet& gpr, const FloatRegisterSet& fpu) : gpr_(gpr), fpu_(fpu) {} static inline RegisterSet All() { return RegisterSet(GeneralRegisterSet::All(), FloatRegisterSet::All()); } static inline RegisterSet Intersect(const RegisterSet& lhs, const RegisterSet& rhs) { return RegisterSet(GeneralRegisterSet::Intersect(lhs.gpr_, rhs.gpr_), FloatRegisterSet::Intersect(lhs.fpu_, rhs.fpu_)); } static inline RegisterSet Union(const RegisterSet& lhs, const RegisterSet& rhs) { return RegisterSet(GeneralRegisterSet::Union(lhs.gpr_, rhs.gpr_), FloatRegisterSet::Union(lhs.fpu_, rhs.fpu_)); } static inline RegisterSet Not(const RegisterSet& in) { return RegisterSet(GeneralRegisterSet::Not(in.gpr_), FloatRegisterSet::Not(in.fpu_)); } static inline RegisterSet VolatileNot(const RegisterSet& in) { return RegisterSet(GeneralRegisterSet::VolatileNot(in.gpr_), FloatRegisterSet::VolatileNot(in.fpu_)); } static inline RegisterSet Volatile() { return RegisterSet(GeneralRegisterSet::Volatile(), FloatRegisterSet::Volatile()); } bool empty() const { return fpu_.empty() && gpr_.empty(); } void clear() { fpu_.clear(); gpr_.clear(); } bool emptyGeneral() const { return gpr_.empty(); } bool emptyFloat() const { return fpu_.empty(); } static constexpr RegTypeName DefaultType = RegTypeName::GPR; constexpr GeneralRegisterSet gprs() const { return gpr_; } GeneralRegisterSet& gprs() { return gpr_; } constexpr FloatRegisterSet fpus() const { return fpu_; } FloatRegisterSet& fpus() { return fpu_; } bool operator==(const RegisterSet& other) const { return other.gpr_ == gpr_ && other.fpu_ == fpu_; } }; // [SMDOC] JIT Register-Set overview // // There are 2 use cases for register sets: // // 1. To serve as a pool of allocatable register. This is useful for working // on the code produced by some stub where free registers are available, or // when we can release some registers. // // 2. To serve as a list of typed registers. This is useful for working with // live registers and to manipulate them with the proper instructions. This // is used by the register allocator to fill the Safepoints. // // These 2 uses cases can be used on top of 3 different backend representation // of register sets, which are either GeneralRegisterSet, FloatRegisterSet, or // RegisterSet (for both). These classes are used to store the bit sets to // represent each register. // // Each use case defines an Accessor class, such as AllocatableSetAccessor or // LiveSetAccessor, which is parameterized with the type of the register // set. These accessors are in charge of manipulating the register set in a // consistent way. // // The RegSetCommonInterface class is used to wrap the accessors with convenient // shortcuts which are based on the accessors. // // Then, to avoid to many levels of complexity while using these interfaces, // shortcut templates are created to make it easy to distinguish between a // register set used for allocating registers, or a register set used for making // a collection of allocated (live) registers. // // This separation exists to prevent mixing LiveSet and AllocatableSet // manipulations of the same register set, and ensure safety while avoiding // false positive. template class AllocatableSet; template class LiveSet; // [SMDOC] JIT Register-Set (Allocatable) // // Base accessors classes have the minimal set of raw methods to manipulate the // register set given as parameter in a consistent manner. These methods are: // // - all: Returns a bit-set of all the register of a specific type // which are present. // // - has: Returns if all the bits needed to take a register are present. // // - takeUnchecked: Subtracts the bits used to represent the register in the // register set. // // - addUnchecked: Adds the bits used to represent the register in the // register set. // The AllocatableSet accessors are used to make a pool of unused // registers. Taking or adding registers should consider the aliasing rules of // the architecture. For example, on ARM, the following piece of code should // work fine, knowing that the double register |d0| is composed of float // registers |s0| and |s1|: // // AllocatableFloatRegisterSet regs; // regs.add(s0); // regs.add(s1); // // d0 is now available. // regs.take(d0); // // These accessors are useful for allocating registers within the functions used // to generate stubs, trampolines, and inline caches (BaselineIC, IonCache). template class AllocatableSetAccessors { public: using RegSet = Set; using RegType = typename RegSet::RegType; using SetType = typename RegSet::SetType; protected: RegSet set_; template SetType all() const { return set_.template allAllocatable(); } public: AllocatableSetAccessors() : set_() {} explicit constexpr AllocatableSetAccessors(SetType set) : set_(set) {} explicit constexpr AllocatableSetAccessors(RegSet set) : set_(set) {} bool has(RegType reg) const { return set_.hasAllocatable(reg); } template bool hasAny(RegType reg) const { return all() != 0; } void addUnchecked(RegType reg) { set_.addAllocatable(reg); } void takeUnchecked(RegType reg) { set_.takeAllocatable(reg); } }; // Specialization of the AllocatableSet accessors for the RegisterSet aggregate. template <> class AllocatableSetAccessors { public: using RegSet = RegisterSet; using RegType = AnyRegister; using SetType = char; protected: RegisterSet set_; template GeneralRegisterSet::SetType allGpr() const { return set_.gprs().allAllocatable(); } template FloatRegisterSet::SetType allFpu() const { return set_.fpus().allAllocatable(); } public: AllocatableSetAccessors() : set_() {} explicit constexpr AllocatableSetAccessors(SetType) = delete; explicit constexpr AllocatableSetAccessors(RegisterSet set) : set_(set) {} bool has(Register reg) const { return set_.gprs().hasAllocatable(reg); } bool has(FloatRegister reg) const { return set_.fpus().hasAllocatable(reg); } void addUnchecked(Register reg) { set_.gprs().addAllocatable(reg); } void addUnchecked(FloatRegister reg) { set_.fpus().addAllocatable(reg); } void takeUnchecked(Register reg) { set_.gprs().takeAllocatable(reg); } void takeUnchecked(FloatRegister reg) { set_.fpus().takeAllocatable(reg); } }; // [SMDOC] JIT Register-Set (Live) // // The LiveSet accessors are used to collect a list of allocated // registers. Taking or adding a register should *not* consider the aliases, as // we care about interpreting the registers with the correct type. For example, // on x64, where one float registers can be interpreted as an Simd128, a Double, // or a Float, adding xmm0 as an Simd128, does not make the register available // as a Double. // // LiveFloatRegisterSet regs; // regs.add(xmm0.asSimd128()); // regs.take(xmm0); // Assert! // // These accessors are useful for recording the result of a register allocator, // such as what the Backtracking allocator do on the Safepoints. template class LiveSetAccessors { public: using RegSet = Set; using RegType = typename RegSet::RegType; using SetType = typename RegSet::SetType; protected: RegSet set_; template SetType all() const { return set_.template allLive(); } public: LiveSetAccessors() : set_() {} explicit constexpr LiveSetAccessors(SetType set) : set_(set) {} explicit constexpr LiveSetAccessors(RegSet set) : set_(set) {} bool has(RegType reg) const { return set_.hasRegisterIndex(reg); } void addUnchecked(RegType reg) { set_.addRegisterIndex(reg); } void takeUnchecked(RegType reg) { set_.takeRegisterIndex(reg); } }; // Specialization of the LiveSet accessors for the RegisterSet aggregate. template <> class LiveSetAccessors { public: using RegSet = RegisterSet; using RegType = AnyRegister; using SetType = char; protected: RegisterSet set_; template GeneralRegisterSet::SetType allGpr() const { return set_.gprs().allLive(); } template FloatRegisterSet::SetType allFpu() const { return set_.fpus().allLive(); } public: LiveSetAccessors() : set_() {} explicit constexpr LiveSetAccessors(SetType) = delete; explicit constexpr LiveSetAccessors(RegisterSet set) : set_(set) {} bool has(Register reg) const { return set_.gprs().hasRegisterIndex(reg); } bool has(FloatRegister reg) const { return set_.fpus().hasRegisterIndex(reg); } void addUnchecked(Register reg) { set_.gprs().addRegisterIndex(reg); } void addUnchecked(FloatRegister reg) { set_.fpus().addRegisterIndex(reg); } void takeUnchecked(Register reg) { set_.gprs().takeRegisterIndex(reg); } void takeUnchecked(FloatRegister reg) { set_.fpus().takeRegisterIndex(reg); } }; #define DEFINE_ACCESSOR_CONSTRUCTORS_(REGSET) \ typedef typename Parent::RegSet RegSet; \ typedef typename Parent::RegType RegType; \ typedef typename Parent::SetType SetType; \ \ constexpr REGSET() : Parent() {} \ explicit constexpr REGSET(SetType set) : Parent(set) {} \ explicit constexpr REGSET(RegSet set) : Parent(set) {} // This class adds checked accessors on top of the unchecked variants defined by // AllocatableSet and LiveSet accessors. Also it defines interface which are // specialized to the register set implementation, such as |getAny| and // |takeAny| variants. template class SpecializedRegSet : public Accessors { using Parent = Accessors; public: DEFINE_ACCESSOR_CONSTRUCTORS_(SpecializedRegSet) SetType bits() const { return this->Parent::set_.bits(); } using Parent::has; using Parent::addUnchecked; void add(RegType reg) { MOZ_ASSERT(!this->has(reg)); addUnchecked(reg); } using Parent::takeUnchecked; void take(RegType reg) { MOZ_ASSERT(this->has(reg)); takeUnchecked(reg); } template bool hasAny() const { return Parent::template all() != 0; } template RegType getFirst() const { SetType set = Parent::template all(); MOZ_ASSERT(set); return RegSet::FirstRegister(set); } template RegType getLast() const { SetType set = Parent::template all(); MOZ_ASSERT(set); return RegSet::LastRegister(set); } template RegType getAny() const { // The choice of first or last here is mostly arbitrary, as they are // about the same speed on popular architectures. We choose first, as // it has the advantage of using the "lower" registers more often. These // registers are sometimes more efficient (e.g. optimized encodings for // EAX on x86). return getFirst(); } template RegType getAnyExcluding(RegType preclude) { if (!this->has(preclude)) { return getAny(); } take(preclude); RegType result = getAny(); add(preclude); return result; } template RegType takeAny() { RegType reg = getAny(); take(reg); return reg; } template RegType takeFirst() { RegType reg = getFirst(); take(reg); return reg; } template RegType takeLast() { RegType reg = getLast(); take(reg); return reg; } ValueOperand takeAnyValue() { #if defined(JS_NUNBOX32) return ValueOperand(takeAny(), takeAny()); #elif defined(JS_PUNBOX64) return ValueOperand(takeAny()); #else # error "Bad architecture" #endif } bool aliases(ValueOperand v) const { #ifdef JS_NUNBOX32 return this->has(v.typeReg()) || this->has(v.payloadReg()); #else return this->has(v.valueReg()); #endif } template RegType takeAnyExcluding(RegType preclude) { RegType reg = getAnyExcluding(preclude); take(reg); return reg; } }; // Specialization of the accessors for the RegisterSet aggregate. template class SpecializedRegSet : public Accessors { using Parent = Accessors; public: DEFINE_ACCESSOR_CONSTRUCTORS_(SpecializedRegSet) GeneralRegisterSet gprs() const { return this->Parent::set_.gprs(); } GeneralRegisterSet& gprs() { return this->Parent::set_.gprs(); } FloatRegisterSet fpus() const { return this->Parent::set_.fpus(); } FloatRegisterSet& fpus() { return this->Parent::set_.fpus(); } bool emptyGeneral() const { return this->Parent::set_.emptyGeneral(); } bool emptyFloat() const { return this->Parent::set_.emptyFloat(); } using Parent::has; bool has(AnyRegister reg) const { return reg.isFloat() ? this->has(reg.fpu()) : this->has(reg.gpr()); } template bool hasAny() const { if (Name == RegTypeName::GPR) { return Parent::template allGpr() != 0; } return Parent::template allFpu() != 0; } using Parent::addUnchecked; void addUnchecked(AnyRegister reg) { if (reg.isFloat()) { addUnchecked(reg.fpu()); } else { addUnchecked(reg.gpr()); } } void add(Register reg) { MOZ_ASSERT(!this->has(reg)); addUnchecked(reg); } void add(FloatRegister reg) { MOZ_ASSERT(!this->has(reg)); addUnchecked(reg); } void add(AnyRegister reg) { if (reg.isFloat()) { add(reg.fpu()); } else { add(reg.gpr()); } } using Parent::takeUnchecked; void takeUnchecked(AnyRegister reg) { if (reg.isFloat()) { takeUnchecked(reg.fpu()); } else { takeUnchecked(reg.gpr()); } } void take(Register reg) { #ifdef DEBUG bool hasReg = this->has(reg); MOZ_ASSERT(hasReg); #endif takeUnchecked(reg); } void take(FloatRegister reg) { MOZ_ASSERT(this->has(reg)); takeUnchecked(reg); } void take(AnyRegister reg) { if (reg.isFloat()) { take(reg.fpu()); } else { take(reg.gpr()); } } Register getAnyGeneral() const { GeneralRegisterSet::SetType set = Parent::template allGpr(); MOZ_ASSERT(set); return GeneralRegisterSet::FirstRegister(set); } template FloatRegister getAnyFloat() const { FloatRegisterSet::SetType set = Parent::template allFpu(); MOZ_ASSERT(set); return FloatRegisterSet::FirstRegister(set); } Register takeAnyGeneral() { Register reg = getAnyGeneral(); take(reg); return reg; } template FloatRegister takeAnyFloat() { FloatRegister reg = getAnyFloat(); take(reg); return reg; } ValueOperand takeAnyValue() { #if defined(JS_NUNBOX32) return ValueOperand(takeAnyGeneral(), takeAnyGeneral()); #elif defined(JS_PUNBOX64) return ValueOperand(takeAnyGeneral()); #else # error "Bad architecture" #endif } }; // Interface which is common to all register set implementations. It overloads // |add|, |take| and |takeUnchecked| methods for types such as |ValueOperand|, // |TypedOrValueRegister|, and |Register64|. template class CommonRegSet : public SpecializedRegSet { typedef SpecializedRegSet Parent; public: DEFINE_ACCESSOR_CONSTRUCTORS_(CommonRegSet) RegSet set() const { return this->Parent::set_; } RegSet& set() { return this->Parent::set_; } bool empty() const { return this->Parent::set_.empty(); } void clear() { this->Parent::set_.clear(); } using Parent::add; void add(ValueOperand value) { #if defined(JS_NUNBOX32) add(value.payloadReg()); add(value.typeReg()); #elif defined(JS_PUNBOX64) add(value.valueReg()); #else # error "Bad architecture" #endif } void add(Register64 reg) { #if JS_BITS_PER_WORD == 32 add(reg.high); add(reg.low); #else add(reg.reg); #endif } using Parent::addUnchecked; void addUnchecked(ValueOperand value) { #if defined(JS_NUNBOX32) addUnchecked(value.payloadReg()); addUnchecked(value.typeReg()); #elif defined(JS_PUNBOX64) addUnchecked(value.valueReg()); #else # error "Bad architecture" #endif } void addUnchecked(Register64 reg) { #if JS_BITS_PER_WORD == 32 take(reg.high); take(reg.low); #else take(reg.reg); #endif } void add(TypedOrValueRegister reg) { if (reg.hasValue()) { add(reg.valueReg()); } else if (reg.hasTyped()) { add(reg.typedReg()); } } using Parent::take; void take(ValueOperand value) { #if defined(JS_NUNBOX32) take(value.payloadReg()); take(value.typeReg()); #elif defined(JS_PUNBOX64) take(value.valueReg()); #else # error "Bad architecture" #endif } void take(TypedOrValueRegister reg) { if (reg.hasValue()) { take(reg.valueReg()); } else if (reg.hasTyped()) { take(reg.typedReg()); } } void take(Register64 reg) { #if JS_BITS_PER_WORD == 32 take(reg.high); take(reg.low); #else take(reg.reg); #endif } using Parent::takeUnchecked; void takeUnchecked(ValueOperand value) { #if defined(JS_NUNBOX32) takeUnchecked(value.payloadReg()); takeUnchecked(value.typeReg()); #elif defined(JS_PUNBOX64) takeUnchecked(value.valueReg()); #else # error "Bad architecture" #endif } void takeUnchecked(TypedOrValueRegister reg) { if (reg.hasValue()) { takeUnchecked(reg.valueReg()); } else if (reg.hasTyped()) { takeUnchecked(reg.typedReg()); } } void takeUnchecked(Register64 reg) { #if JS_BITS_PER_WORD == 32 takeUnchecked(reg.high); takeUnchecked(reg.low); #else takeUnchecked(reg.reg); #endif } }; // These classes do not provide any additional members, they only use their // constructors to forward to the common interface for all register sets. The // only benefit of these classes is to provide user friendly names. template class LiveSet : public CommonRegSet, Set> { typedef CommonRegSet, Set> Parent; public: DEFINE_ACCESSOR_CONSTRUCTORS_(LiveSet) }; template class AllocatableSet : public CommonRegSet, Set> { typedef CommonRegSet, Set> Parent; public: DEFINE_ACCESSOR_CONSTRUCTORS_(AllocatableSet) LiveSet asLiveSet() const { return LiveSet(this->set()); } }; #define DEFINE_ACCESSOR_CONSTRUCTORS_FOR_REGISTERSET_(REGSET) \ typedef Parent::RegSet RegSet; \ typedef Parent::RegType RegType; \ typedef Parent::SetType SetType; \ \ constexpr REGSET() : Parent() {} \ explicit constexpr REGSET(SetType) = delete; \ explicit constexpr REGSET(RegSet set) : Parent(set) {} \ constexpr REGSET(GeneralRegisterSet gpr, FloatRegisterSet fpu) \ : Parent(RegisterSet(gpr, fpu)) {} \ REGSET(REGSET gpr, REGSET fpu) \ : Parent(RegisterSet(gpr.set(), fpu.set())) {} template <> class LiveSet : public CommonRegSet, RegisterSet> { // Note: We have to provide a qualified name for LiveSetAccessors, as it is // interpreted as being the specialized class name inherited from the parent // class specialization. typedef CommonRegSet, RegisterSet> Parent; public: DEFINE_ACCESSOR_CONSTRUCTORS_FOR_REGISTERSET_(LiveSet) }; template <> class AllocatableSet : public CommonRegSet, RegisterSet> { // Note: We have to provide a qualified name for AllocatableSetAccessors, as // it is interpreted as being the specialized class name inherited from the // parent class specialization. typedef CommonRegSet, RegisterSet> Parent; public: DEFINE_ACCESSOR_CONSTRUCTORS_FOR_REGISTERSET_(AllocatableSet) LiveSet asLiveSet() const { return LiveSet(this->set()); } }; #undef DEFINE_ACCESSOR_CONSTRUCTORS_FOR_REGISTERSET_ #undef DEFINE_ACCESSOR_CONSTRUCTORS_ using AllocatableGeneralRegisterSet = AllocatableSet; using AllocatableFloatRegisterSet = AllocatableSet; using AllocatableRegisterSet = AllocatableSet; using LiveGeneralRegisterSet = LiveSet; using LiveFloatRegisterSet = LiveSet; using LiveRegisterSet = LiveSet; // iterates in whatever order happens to be convenient. // Use TypedRegisterBackwardIterator or TypedRegisterForwardIterator if a // specific order is required. template class TypedRegisterIterator { LiveSet> regset_; public: explicit TypedRegisterIterator(TypedRegisterSet regset) : regset_(regset) {} explicit TypedRegisterIterator(LiveSet> regset) : regset_(regset) {} TypedRegisterIterator(const TypedRegisterIterator& other) : regset_(other.regset_) {} bool more() const { return !regset_.empty(); } TypedRegisterIterator& operator++() { regset_.template takeAny(); return *this; } T operator*() const { return regset_.template getAny(); } }; // iterates backwards, that is, rn to r0 template class TypedRegisterBackwardIterator { LiveSet> regset_; public: explicit TypedRegisterBackwardIterator(TypedRegisterSet regset) : regset_(regset) {} explicit TypedRegisterBackwardIterator(LiveSet> regset) : regset_(regset) {} TypedRegisterBackwardIterator(const TypedRegisterBackwardIterator& other) : regset_(other.regset_) {} bool more() const { return !regset_.empty(); } TypedRegisterBackwardIterator& operator++() { regset_.template takeLast(); return *this; } T operator*() const { return regset_.template getLast(); } }; // iterates forwards, that is r0 to rn template class TypedRegisterForwardIterator { LiveSet> regset_; public: explicit TypedRegisterForwardIterator(TypedRegisterSet regset) : regset_(regset) {} explicit TypedRegisterForwardIterator(LiveSet> regset) : regset_(regset) {} TypedRegisterForwardIterator(const TypedRegisterForwardIterator& other) : regset_(other.regset_) {} bool more() const { return !regset_.empty(); } TypedRegisterForwardIterator& operator++() { regset_.template takeFirst(); return *this; } T operator*() const { return regset_.template getFirst(); } }; using GeneralRegisterIterator = TypedRegisterIterator; using FloatRegisterIterator = TypedRegisterIterator; using GeneralRegisterBackwardIterator = TypedRegisterBackwardIterator; using FloatRegisterBackwardIterator = TypedRegisterBackwardIterator; using GeneralRegisterForwardIterator = TypedRegisterForwardIterator; using FloatRegisterForwardIterator = TypedRegisterForwardIterator; class AnyRegisterIterator { GeneralRegisterIterator geniter_; FloatRegisterIterator floatiter_; public: AnyRegisterIterator() : geniter_(GeneralRegisterSet::All()), floatiter_(FloatRegisterSet::All()) {} AnyRegisterIterator(GeneralRegisterSet genset, FloatRegisterSet floatset) : geniter_(genset), floatiter_(floatset) {} explicit AnyRegisterIterator(const RegisterSet& set) : geniter_(set.gpr_), floatiter_(set.fpu_) {} explicit AnyRegisterIterator(const LiveSet& set) : geniter_(set.gprs()), floatiter_(set.fpus()) {} AnyRegisterIterator(const AnyRegisterIterator& other) = default; bool more() const { return geniter_.more() || floatiter_.more(); } AnyRegisterIterator& operator++() { if (geniter_.more()) { ++geniter_; } else { ++floatiter_; } return *this; } AnyRegister operator*() const { if (geniter_.more()) { return AnyRegister(*geniter_); } return AnyRegister(*floatiter_); } }; class ABIArg { public: enum Kind { GPR, #ifdef JS_CODEGEN_REGISTER_PAIR GPR_PAIR, #endif FPU, Stack, Uninitialized = -1 }; private: Kind kind_; union { Register::Code gpr_; FloatRegister::Code fpu_; uint32_t offset_; } u; public: ABIArg() : kind_(Uninitialized) { u.offset_ = -1; } explicit ABIArg(Register gpr) : kind_(GPR) { u.gpr_ = gpr.code(); } explicit ABIArg(Register gprLow, Register gprHigh) { #if defined(JS_CODEGEN_REGISTER_PAIR) kind_ = GPR_PAIR; #else MOZ_CRASH("Unsupported type of ABI argument."); #endif u.gpr_ = gprLow.code(); MOZ_ASSERT(u.gpr_ % 2 == 0); MOZ_ASSERT(u.gpr_ + 1 == gprHigh.code()); } explicit ABIArg(FloatRegister fpu) : kind_(FPU) { u.fpu_ = fpu.code(); } explicit ABIArg(uint32_t offset) : kind_(Stack) { u.offset_ = offset; } Kind kind() const { MOZ_ASSERT(kind_ != Uninitialized); return kind_; } #ifdef JS_CODEGEN_REGISTER_PAIR bool isGeneralRegPair() const { return kind() == GPR_PAIR; } #else bool isGeneralRegPair() const { return false; } #endif Register gpr() const { MOZ_ASSERT(kind() == GPR); return Register::FromCode(u.gpr_); } Register64 gpr64() const { #ifdef JS_PUNBOX64 return Register64(gpr()); #else return Register64(oddGpr(), evenGpr()); #endif } Register evenGpr() const { MOZ_ASSERT(isGeneralRegPair()); return Register::FromCode(u.gpr_); } Register oddGpr() const { MOZ_ASSERT(isGeneralRegPair()); return Register::FromCode(u.gpr_ + 1); } FloatRegister fpu() const { MOZ_ASSERT(kind() == FPU); return FloatRegister::FromCode(u.fpu_); } uint32_t offsetFromArgBase() const { MOZ_ASSERT(kind() == Stack); return u.offset_; } bool argInRegister() const { return kind() != Stack; } AnyRegister reg() const { return kind() == GPR ? AnyRegister(gpr()) : AnyRegister(fpu()); } bool operator==(const ABIArg& rhs) const { if (kind_ != rhs.kind_) { return false; } switch (kind_) { case GPR: return u.gpr_ == rhs.u.gpr_; #if defined(JS_CODEGEN_REGISTER_PAIR) case GPR_PAIR: return u.gpr_ == rhs.u.gpr_; #endif case FPU: return u.fpu_ == rhs.u.fpu_; case Stack: return u.offset_ == rhs.u.offset_; case Uninitialized: return true; } MOZ_CRASH("Invalid value for ABIArg kind"); } bool operator!=(const ABIArg& rhs) const { return !(*this == rhs); } }; // Get the set of registers which should be saved by a block of code which // clobbers all registers besides |unused|, but does not clobber floating point // registers. inline LiveGeneralRegisterSet SavedNonVolatileRegisters( const AllocatableGeneralRegisterSet& unused) { LiveGeneralRegisterSet result; for (GeneralRegisterIterator iter(GeneralRegisterSet::NonVolatile()); iter.more(); ++iter) { Register reg = *iter; if (!unused.has(reg)) { result.add(reg); } } // Some platforms require the link register to be saved, if calls can be made. #if defined(JS_CODEGEN_ARM) result.add(Register::FromCode(Registers::lr)); #elif defined(JS_CODEGEN_ARM64) result.add(Register::FromCode(Registers::lr)); #elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) result.add(Register::FromCode(Registers::ra)); #endif return result; } } // namespace jit } // namespace js #endif /* jit_RegisterSets_h */