// Copyright 2016, VIXL authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of ARM Limited nor the names of its contributors may be // used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef VIXL_AARCH64_OPERANDS_AARCH64_H_ #define VIXL_AARCH64_OPERANDS_AARCH64_H_ #include #include #include "instructions-aarch64.h" #include "registers-aarch64.h" namespace vixl { namespace aarch64 { // Lists of registers. class CPURegList { public: explicit CPURegList(CPURegister reg1, CPURegister reg2 = NoCPUReg, CPURegister reg3 = NoCPUReg, CPURegister reg4 = NoCPUReg) : list_(reg1.GetBit() | reg2.GetBit() | reg3.GetBit() | reg4.GetBit()), size_(reg1.GetSizeInBits()), type_(reg1.GetType()) { VIXL_ASSERT(AreSameSizeAndType(reg1, reg2, reg3, reg4)); VIXL_ASSERT(IsValid()); } constexpr CPURegList(CPURegister::RegisterType type, unsigned size, RegList list) : list_(list), size_(size), type_(type) { #ifndef PANDA_BUILD VIXL_ASSERT(IsValid()); #endif } constexpr CPURegList(CPURegister::RegisterType type, unsigned size, unsigned first_reg, unsigned last_reg) : list_((UINT64_C(1) << (last_reg + 1)) - 1), size_(size), type_(type) { VIXL_ASSERT( ((type == CPURegister::kRegister) && (last_reg < kNumberOfRegisters)) || ((type == CPURegister::kVRegister) && (last_reg < kNumberOfVRegisters))); VIXL_ASSERT(last_reg >= first_reg); list_ &= ~((UINT64_C(1) << first_reg) - 1); #ifndef PANDA_BUILD VIXL_ASSERT(IsValid()); #endif } // Construct an empty CPURegList with the specified size and type. If `size` // is CPURegister::kUnknownSize and the register type requires a size, a valid // but unspecified default will be picked. static CPURegList Empty(CPURegister::RegisterType type, unsigned size = CPURegister::kUnknownSize) { return CPURegList(type, GetDefaultSizeFor(type, size), 0); } // Construct a CPURegList with all possible registers with the specified size // and type. If `size` is CPURegister::kUnknownSize and the register type // requires a size, a valid but unspecified default will be picked. static CPURegList All(CPURegister::RegisterType type, unsigned size = CPURegister::kUnknownSize) { unsigned number_of_registers = (CPURegister::GetMaxCodeFor(type) + 1); RegList list = (static_cast(1) << number_of_registers) - 1; if (type == CPURegister::kRegister) { // GetMaxCodeFor(kRegister) ignores SP, so explicitly include it. list |= (static_cast(1) << kSPRegInternalCode); } return CPURegList(type, GetDefaultSizeFor(type, size), list); } CPURegister::RegisterType GetType() const { VIXL_ASSERT(IsValid()); return type_; } VIXL_DEPRECATED("GetType", CPURegister::RegisterType type() const) { return GetType(); } CPURegister::RegisterBank GetBank() const { return CPURegister::GetBankFor(GetType()); } // Combine another CPURegList into this one. Registers that already exist in // this list are left unchanged. The type and size of the registers in the // 'other' list must match those in this list. void Combine(const CPURegList& other) { VIXL_ASSERT(IsValid()); VIXL_ASSERT(other.GetType() == type_); VIXL_ASSERT(other.GetRegisterSizeInBits() == size_); list_ |= other.GetList(); } // Remove every register in the other CPURegList from this one. Registers that // do not exist in this list are ignored. The type and size of the registers // in the 'other' list must match those in this list. void Remove(const CPURegList& other) { VIXL_ASSERT(IsValid()); VIXL_ASSERT(other.GetType() == type_); VIXL_ASSERT(other.GetRegisterSizeInBits() == size_); list_ &= ~other.GetList(); } // Variants of Combine and Remove which take a single register. void Combine(const CPURegister& other) { VIXL_ASSERT(other.GetType() == type_); VIXL_ASSERT(other.GetSizeInBits() == size_); Combine(other.GetCode()); } void Remove(const CPURegister& other) { VIXL_ASSERT(other.GetType() == type_); VIXL_ASSERT(other.GetSizeInBits() == size_); Remove(other.GetCode()); } // Variants of Combine and Remove which take a single register by its code; // the type and size of the register is inferred from this list. void Combine(int code) { VIXL_ASSERT(IsValid()); VIXL_ASSERT(CPURegister(code, size_, type_).IsValid()); list_ |= (UINT64_C(1) << code); } void Remove(int code) { VIXL_ASSERT(IsValid()); VIXL_ASSERT(CPURegister(code, size_, type_).IsValid()); list_ &= ~(UINT64_C(1) << code); } static CPURegList Union(const CPURegList& list_1, const CPURegList& list_2) { VIXL_ASSERT(list_1.type_ == list_2.type_); VIXL_ASSERT(list_1.size_ == list_2.size_); return CPURegList(list_1.type_, list_1.size_, list_1.list_ | list_2.list_); } static CPURegList Union(const CPURegList& list_1, const CPURegList& list_2, const CPURegList& list_3); static CPURegList Union(const CPURegList& list_1, const CPURegList& list_2, const CPURegList& list_3, const CPURegList& list_4); static CPURegList Intersection(const CPURegList& list_1, const CPURegList& list_2) { VIXL_ASSERT(list_1.type_ == list_2.type_); VIXL_ASSERT(list_1.size_ == list_2.size_); return CPURegList(list_1.type_, list_1.size_, list_1.list_ & list_2.list_); } static CPURegList Intersection(const CPURegList& list_1, const CPURegList& list_2, const CPURegList& list_3); static CPURegList Intersection(const CPURegList& list_1, const CPURegList& list_2, const CPURegList& list_3, const CPURegList& list_4); bool Overlaps(const CPURegList& other) const { return (type_ == other.type_) && ((list_ & other.list_) != 0); } constexpr RegList GetList() const { #ifndef PANDA_BUILD VIXL_ASSERT(IsValid()); #endif return list_; } VIXL_DEPRECATED("GetList", RegList list() const) { return GetList(); } void SetList(RegList new_list) { VIXL_ASSERT(IsValid()); list_ = new_list; } VIXL_DEPRECATED("SetList", void set_list(RegList new_list)) { return SetList(new_list); } // Remove all callee-saved registers from the list. This can be useful when // preparing registers for an AAPCS64 function call, for example. void RemoveCalleeSaved(); // Find the register in this list that appears in `mask` with the lowest or // highest code, remove it from the list and return it as a CPURegister. If // the list is empty, leave it unchanged and return NoCPUReg. CPURegister PopLowestIndex(RegList mask = ~static_cast(0)); CPURegister PopHighestIndex(RegList mask = ~static_cast(0)); // AAPCS64 callee-saved registers. static CPURegList GetCalleeSaved(unsigned size = kXRegSize); static CPURegList GetCalleeSavedV(unsigned size = kDRegSize); // AAPCS64 caller-saved registers. Note that this includes lr. // TODO(all): Determine how we handle d8-d15 being callee-saved, but the top // 64-bits being caller-saved. static CPURegList GetCallerSaved(unsigned size = kXRegSize); static CPURegList GetCallerSavedV(unsigned size = kDRegSize); bool IsEmpty() const { VIXL_ASSERT(IsValid()); return list_ == 0; } bool IncludesAliasOf(const CPURegister& other) const { VIXL_ASSERT(IsValid()); return (GetBank() == other.GetBank()) && IncludesAliasOf(other.GetCode()); } bool IncludesAliasOf(int code) const { VIXL_ASSERT(IsValid()); return (((static_cast(1) << code) & list_) != 0); } int GetCount() const { VIXL_ASSERT(IsValid()); return CountSetBits(list_); } VIXL_DEPRECATED("GetCount", int Count()) const { return GetCount(); } int GetRegisterSizeInBits() const { VIXL_ASSERT(IsValid()); return size_; } VIXL_DEPRECATED("GetRegisterSizeInBits", int RegisterSizeInBits() const) { return GetRegisterSizeInBits(); } int GetRegisterSizeInBytes() const { int size_in_bits = GetRegisterSizeInBits(); VIXL_ASSERT((size_in_bits % 8) == 0); return size_in_bits / 8; } VIXL_DEPRECATED("GetRegisterSizeInBytes", int RegisterSizeInBytes() const) { return GetRegisterSizeInBytes(); } unsigned GetTotalSizeInBytes() const { VIXL_ASSERT(IsValid()); return GetRegisterSizeInBytes() * GetCount(); } VIXL_DEPRECATED("GetTotalSizeInBytes", unsigned TotalSizeInBytes() const) { return GetTotalSizeInBytes(); } private: // If `size` is CPURegister::kUnknownSize and the type requires a known size, // then return an arbitrary-but-valid size. // // Otherwise, the size is checked for validity and returned unchanged. static unsigned GetDefaultSizeFor(CPURegister::RegisterType type, unsigned size) { if (size == CPURegister::kUnknownSize) { if (type == CPURegister::kRegister) size = kXRegSize; if (type == CPURegister::kVRegister) size = kQRegSize; // All other types require kUnknownSize. } VIXL_ASSERT(CPURegister(0, size, type).IsValid()); return size; } RegList list_; int size_; CPURegister::RegisterType type_; bool IsValid() const; }; // AAPCS64 callee-saved registers. constexpr CPURegList kCalleeSaved = CPURegList(CPURegister::kRegister, kXRegSize, 19, 28); constexpr CPURegList kCalleeSavedV = CPURegList(CPURegister::kVRegister, kDRegSize, 8, 15); // AAPCS64 caller-saved registers. Note that this includes lr. constexpr CPURegList kCallerSaved = CPURegList(CPURegister::kRegister, kXRegSize, 0, 18); constexpr CPURegList kCallerSavedV = CPURegList(CPURegister::kVRegister, kDRegSize, 0xffff00ff); class IntegerOperand; // Operand. class Operand { public: // # // where is int64_t. // This is allowed to be an implicit constructor because Operand is // a wrapper class that doesn't normally perform any type conversion. Operand(int64_t immediate); // NOLINT(runtime/explicit) Operand(IntegerOperand immediate); // NOLINT(runtime/explicit) // rm, { #} // where is one of {LSL, LSR, ASR, ROR}. // is uint6_t. // This is allowed to be an implicit constructor because Operand is // a wrapper class that doesn't normally perform any type conversion. Operand(Register reg, Shift shift = LSL, unsigned shift_amount = 0); // NOLINT(runtime/explicit) // rm, { {#}} // where is one of {UXTB, UXTH, UXTW, UXTX, SXTB, SXTH, SXTW, SXTX}. // is uint2_t. explicit Operand(Register reg, Extend extend, unsigned shift_amount = 0); bool IsImmediate() const; bool IsPlainRegister() const; bool IsShiftedRegister() const; bool IsExtendedRegister() const; bool IsZero() const; // This returns an LSL shift (<= 4) operand as an equivalent extend operand, // which helps in the encoding of instructions that use the stack pointer. Operand ToExtendedRegister() const; int64_t GetImmediate() const { VIXL_ASSERT(IsImmediate()); return immediate_; } VIXL_DEPRECATED("GetImmediate", int64_t immediate() const) { return GetImmediate(); } int64_t GetEquivalentImmediate() const { return IsZero() ? 0 : GetImmediate(); } Register GetRegister() const { VIXL_ASSERT(IsShiftedRegister() || IsExtendedRegister()); return reg_; } VIXL_DEPRECATED("GetRegister", Register reg() const) { return GetRegister(); } Register GetBaseRegister() const { return GetRegister(); } Shift GetShift() const { VIXL_ASSERT(IsShiftedRegister()); return shift_; } VIXL_DEPRECATED("GetShift", Shift shift() const) { return GetShift(); } Extend GetExtend() const { VIXL_ASSERT(IsExtendedRegister()); return extend_; } VIXL_DEPRECATED("GetExtend", Extend extend() const) { return GetExtend(); } unsigned GetShiftAmount() const { VIXL_ASSERT(IsShiftedRegister() || IsExtendedRegister()); return shift_amount_; } VIXL_DEPRECATED("GetShiftAmount", unsigned shift_amount() const) { return GetShiftAmount(); } private: int64_t immediate_; Register reg_; Shift shift_; Extend extend_; unsigned shift_amount_; }; // MemOperand represents the addressing mode of a load or store instruction. // In assembly syntax, MemOperands are normally denoted by one or more elements // inside or around square brackets. class MemOperand { public: // Creates an invalid `MemOperand`. MemOperand(); explicit MemOperand(Register base, int64_t offset = 0, AddrMode addrmode = Offset); MemOperand(Register base, Register regoffset, Shift shift = LSL, unsigned shift_amount = 0); MemOperand(Register base, Register regoffset, Extend extend, unsigned shift_amount = 0); MemOperand(Register base, const Operand& offset, AddrMode addrmode = Offset); const Register& GetBaseRegister() const { return base_; } // If the MemOperand has a register offset, return it. (This also applies to // pre- and post-index modes.) Otherwise, return NoReg. const Register& GetRegisterOffset() const { return regoffset_; } // If the MemOperand has an immediate offset, return it. (This also applies to // pre- and post-index modes.) Otherwise, return 0. int64_t GetOffset() const { return offset_; } AddrMode GetAddrMode() const { return addrmode_; } Shift GetShift() const { return shift_; } Extend GetExtend() const { return extend_; } unsigned GetShiftAmount() const { // Extend modes can also encode a shift for some instructions. VIXL_ASSERT((GetShift() != NO_SHIFT) || (GetExtend() != NO_EXTEND)); return shift_amount_; } // True for MemOperands which represent something like [x0]. // Currently, this will also return true for [x0, #0], because MemOperand has // no way to distinguish the two. bool IsPlainRegister() const; // True for MemOperands which represent something like [x0], or for compound // MemOperands which are functionally equivalent, such as [x0, #0], [x0, xzr] // or [x0, wzr, UXTW #3]. bool IsEquivalentToPlainRegister() const; // True for immediate-offset (but not indexed) MemOperands. bool IsImmediateOffset() const; // True for register-offset (but not indexed) MemOperands. bool IsRegisterOffset() const; // True for immediate or register pre-indexed MemOperands. bool IsPreIndex() const; // True for immediate or register post-indexed MemOperands. bool IsPostIndex() const; // True for immediate pre-indexed MemOperands, [reg, #imm]! bool IsImmediatePreIndex() const; // True for immediate post-indexed MemOperands, [reg], #imm bool IsImmediatePostIndex() const; void AddOffset(int64_t offset); bool IsValid() const { return base_.IsValid() && ((addrmode_ == Offset) || (addrmode_ == PreIndex) || (addrmode_ == PostIndex)) && ((shift_ == NO_SHIFT) || (extend_ == NO_EXTEND)) && ((offset_ == 0) || !regoffset_.IsValid()); } bool Equals(const MemOperand& other) const { return base_.Is(other.base_) && regoffset_.Is(other.regoffset_) && (offset_ == other.offset_) && (addrmode_ == other.addrmode_) && (shift_ == other.shift_) && (extend_ == other.extend_) && (shift_amount_ == other.shift_amount_); } private: Register base_; Register regoffset_; int64_t offset_; AddrMode addrmode_; Shift shift_; Extend extend_; unsigned shift_amount_; }; // SVE supports memory operands which don't make sense to the core ISA, such as // scatter-gather forms, in which either the base or offset registers are // vectors. This class exists to avoid complicating core-ISA code with // SVE-specific behaviour. // // Note that SVE does not support any pre- or post-index modes. class SVEMemOperand { public: // "vector-plus-immediate", like [z0.s, #21] explicit SVEMemOperand(ZRegister base, uint64_t offset = 0) : base_(base), regoffset_(NoReg), offset_(RawbitsToInt64(offset)), mod_(NO_SVE_OFFSET_MODIFIER), shift_amount_(0) { VIXL_ASSERT(IsVectorPlusImmediate()); VIXL_ASSERT(IsValid()); } // "scalar-plus-immediate", like [x0], [x0, #42] or [x0, #42, MUL_VL] // The only supported modifiers are NO_SVE_OFFSET_MODIFIER or SVE_MUL_VL. // // Note that VIXL cannot currently distinguish between `SVEMemOperand(x0)` and // `SVEMemOperand(x0, 0)`. This is only significant in scalar-plus-scalar // instructions where xm defaults to xzr. However, users should not rely on // `SVEMemOperand(x0, 0)` being accepted in such cases. explicit SVEMemOperand(Register base, uint64_t offset = 0, SVEOffsetModifier mod = NO_SVE_OFFSET_MODIFIER) : base_(base), regoffset_(NoReg), offset_(RawbitsToInt64(offset)), mod_(mod), shift_amount_(0) { VIXL_ASSERT(IsScalarPlusImmediate()); VIXL_ASSERT(IsValid()); } // "scalar-plus-scalar", like [x0, x1] // "scalar-plus-vector", like [x0, z1.d] SVEMemOperand(Register base, CPURegister offset) : base_(base), regoffset_(offset), offset_(0), mod_(NO_SVE_OFFSET_MODIFIER), shift_amount_(0) { VIXL_ASSERT(IsScalarPlusScalar() || IsScalarPlusVector()); if (offset.IsZero()) VIXL_ASSERT(IsEquivalentToScalar()); VIXL_ASSERT(IsValid()); } // "scalar-plus-vector", like [x0, z1.d, UXTW] // The type of `mod` can be any `SVEOffsetModifier` (other than LSL), or a // corresponding `Extend` value. template SVEMemOperand(Register base, ZRegister offset, M mod) : base_(base), regoffset_(offset), offset_(0), mod_(GetSVEOffsetModifierFor(mod)), shift_amount_(0) { VIXL_ASSERT(mod_ != SVE_LSL); // LSL requires an explicit shift amount. VIXL_ASSERT(IsScalarPlusVector()); VIXL_ASSERT(IsValid()); } // "scalar-plus-scalar", like [x0, x1, LSL #1] // "scalar-plus-vector", like [x0, z1.d, LSL #2] // The type of `mod` can be any `SVEOffsetModifier`, or a corresponding // `Shift` or `Extend` value. template SVEMemOperand(Register base, CPURegister offset, M mod, unsigned shift_amount) : base_(base), regoffset_(offset), offset_(0), mod_(GetSVEOffsetModifierFor(mod)), shift_amount_(shift_amount) { VIXL_ASSERT(IsValid()); } // "vector-plus-scalar", like [z0.d, x0] SVEMemOperand(ZRegister base, Register offset) : base_(base), regoffset_(offset), offset_(0), mod_(NO_SVE_OFFSET_MODIFIER), shift_amount_(0) { VIXL_ASSERT(IsValid()); VIXL_ASSERT(IsVectorPlusScalar()); } // "vector-plus-vector", like [z0.d, z1.d, UXTW] template SVEMemOperand(ZRegister base, ZRegister offset, M mod = NO_SVE_OFFSET_MODIFIER, unsigned shift_amount = 0) : base_(base), regoffset_(offset), offset_(0), mod_(GetSVEOffsetModifierFor(mod)), shift_amount_(shift_amount) { VIXL_ASSERT(IsValid()); VIXL_ASSERT(IsVectorPlusVector()); } // True for SVEMemOperands which represent something like [x0]. // This will also return true for [x0, #0], because there is no way // to distinguish the two. bool IsPlainScalar() const { return IsScalarPlusImmediate() && (offset_ == 0); } // True for SVEMemOperands which represent something like [x0], or for // compound SVEMemOperands which are functionally equivalent, such as // [x0, #0], [x0, xzr] or [x0, wzr, UXTW #3]. bool IsEquivalentToScalar() const; // True for SVEMemOperands like [x0], [x0, #0], false for [x0, xzr] and // similar. bool IsPlainRegister() const; bool IsScalarPlusImmediate() const { return base_.IsX() && regoffset_.IsNone() && ((mod_ == NO_SVE_OFFSET_MODIFIER) || IsMulVl()); } bool IsScalarPlusScalar() const { // SVE offers no extend modes for scalar-plus-scalar, so both registers must // be X registers. return base_.IsX() && regoffset_.IsX() && ((mod_ == NO_SVE_OFFSET_MODIFIER) || (mod_ == SVE_LSL)); } bool IsScalarPlusVector() const { // The modifier can be LSL or an an extend mode (UXTW or SXTW) here. Unlike // in the core ISA, these extend modes do not imply an S-sized lane, so the // modifier is independent from the lane size. The architecture describes // [US]XTW with a D-sized lane as an "unpacked" offset. return base_.IsX() && regoffset_.IsZRegister() && (regoffset_.IsLaneSizeS() || regoffset_.IsLaneSizeD()) && !IsMulVl(); } bool IsVectorPlusImmediate() const { return base_.IsZRegister() && (base_.IsLaneSizeS() || base_.IsLaneSizeD()) && regoffset_.IsNone() && (mod_ == NO_SVE_OFFSET_MODIFIER); } bool IsVectorPlusScalar() const { return base_.IsZRegister() && regoffset_.IsX() && (base_.IsLaneSizeS() || base_.IsLaneSizeD()); } bool IsVectorPlusVector() const { return base_.IsZRegister() && regoffset_.IsZRegister() && (offset_ == 0) && AreSameFormat(base_, regoffset_) && (base_.IsLaneSizeS() || base_.IsLaneSizeD()); } bool IsContiguous() const { return !IsScatterGather(); } bool IsScatterGather() const { return base_.IsZRegister() || regoffset_.IsZRegister(); } // TODO: If necessary, add helpers like `HasScalarBase()`. Register GetScalarBase() const { VIXL_ASSERT(base_.IsX()); return Register(base_); } ZRegister GetVectorBase() const { VIXL_ASSERT(base_.IsZRegister()); VIXL_ASSERT(base_.HasLaneSize()); return ZRegister(base_); } Register GetScalarOffset() const { VIXL_ASSERT(regoffset_.IsRegister()); return Register(regoffset_); } ZRegister GetVectorOffset() const { VIXL_ASSERT(regoffset_.IsZRegister()); VIXL_ASSERT(regoffset_.HasLaneSize()); return ZRegister(regoffset_); } int64_t GetImmediateOffset() const { VIXL_ASSERT(regoffset_.IsNone()); return offset_; } SVEOffsetModifier GetOffsetModifier() const { return mod_; } unsigned GetShiftAmount() const { return shift_amount_; } bool IsEquivalentToLSL(unsigned amount) const { if (shift_amount_ != amount) return false; if (amount == 0) { // No-shift is equivalent to "LSL #0". return ((mod_ == SVE_LSL) || (mod_ == NO_SVE_OFFSET_MODIFIER)); } return mod_ == SVE_LSL; } bool IsMulVl() const { return mod_ == SVE_MUL_VL; } bool IsValid() const; private: // Allow standard `Shift` and `Extend` arguments to be used. SVEOffsetModifier GetSVEOffsetModifierFor(Shift shift) { if (shift == LSL) return SVE_LSL; if (shift == NO_SHIFT) return NO_SVE_OFFSET_MODIFIER; // SVE does not accept any other shift. VIXL_UNIMPLEMENTED(); return NO_SVE_OFFSET_MODIFIER; } SVEOffsetModifier GetSVEOffsetModifierFor(Extend extend = NO_EXTEND) { if (extend == UXTW) return SVE_UXTW; if (extend == SXTW) return SVE_SXTW; if (extend == NO_EXTEND) return NO_SVE_OFFSET_MODIFIER; // SVE does not accept any other extend mode. VIXL_UNIMPLEMENTED(); return NO_SVE_OFFSET_MODIFIER; } SVEOffsetModifier GetSVEOffsetModifierFor(SVEOffsetModifier mod) { return mod; } CPURegister base_; CPURegister regoffset_; int64_t offset_; SVEOffsetModifier mod_; unsigned shift_amount_; }; // Represent a signed or unsigned integer operand. // // This is designed to make instructions which naturally accept a _signed_ // immediate easier to implement and use, when we also want users to be able to // specify raw-bits values (such as with hexadecimal constants). The advantage // of this class over a simple uint64_t (with implicit C++ sign-extension) is // that this class can strictly check the range of allowed values. With a simple // uint64_t, it is impossible to distinguish -1 from UINT64_MAX. // // For example, these instructions are equivalent: // // __ Insr(z0.VnB(), -1); // __ Insr(z0.VnB(), 0xff); // // ... as are these: // // __ Insr(z0.VnD(), -1); // __ Insr(z0.VnD(), 0xffffffffffffffff); // // ... but this is invalid: // // __ Insr(z0.VnB(), 0xffffffffffffffff); // Too big for B-sized lanes. class IntegerOperand { public: #define VIXL_INT_TYPES(V) \ V(char) V(short) V(int) V(long) V(long long) // NOLINT(runtime/int) #define VIXL_DECL_INT_OVERLOADS(T) \ /* These are allowed to be implicit constructors because this is a */ \ /* wrapper class that doesn't normally perform any type conversion. */ \ IntegerOperand(signed T immediate) /* NOLINT(runtime/explicit) */ \ : raw_bits_(immediate), /* Allow implicit sign-extension. */ \ is_negative_(immediate < 0) {} \ IntegerOperand(unsigned T immediate) /* NOLINT(runtime/explicit) */ \ : raw_bits_(immediate), is_negative_(false) {} VIXL_INT_TYPES(VIXL_DECL_INT_OVERLOADS) #undef VIXL_DECL_INT_OVERLOADS #undef VIXL_INT_TYPES // TODO: `Operand` can currently only hold an int64_t, so some large, unsigned // values will be misrepresented here. explicit IntegerOperand(const Operand& operand) : raw_bits_(operand.GetEquivalentImmediate()), is_negative_(operand.GetEquivalentImmediate() < 0) {} bool IsIntN(unsigned n) const { return is_negative_ ? vixl::IsIntN(n, RawbitsToInt64(raw_bits_)) : vixl::IsIntN(n, raw_bits_); } bool IsUintN(unsigned n) const { return !is_negative_ && vixl::IsUintN(n, raw_bits_); } bool IsUint8() const { return IsUintN(8); } bool IsUint16() const { return IsUintN(16); } bool IsUint32() const { return IsUintN(32); } bool IsUint64() const { return IsUintN(64); } bool IsInt8() const { return IsIntN(8); } bool IsInt16() const { return IsIntN(16); } bool IsInt32() const { return IsIntN(32); } bool IsInt64() const { return IsIntN(64); } bool FitsInBits(unsigned n) const { return is_negative_ ? IsIntN(n) : IsUintN(n); } bool FitsInLane(const CPURegister& zd) const { return FitsInBits(zd.GetLaneSizeInBits()); } bool FitsInSignedLane(const CPURegister& zd) const { return IsIntN(zd.GetLaneSizeInBits()); } bool FitsInUnsignedLane(const CPURegister& zd) const { return IsUintN(zd.GetLaneSizeInBits()); } // Cast a value in the range [INT_MIN, UINT_MAX] to an unsigned integer // in the range [0, UINT_MAX] (using two's complement mapping). uint64_t AsUintN(unsigned n) const { VIXL_ASSERT(FitsInBits(n)); return raw_bits_ & GetUintMask(n); } uint8_t AsUint8() const { return static_cast(AsUintN(8)); } uint16_t AsUint16() const { return static_cast(AsUintN(16)); } uint32_t AsUint32() const { return static_cast(AsUintN(32)); } uint64_t AsUint64() const { return AsUintN(64); } // Cast a value in the range [INT_MIN, UINT_MAX] to a signed integer in // the range [INT_MIN, INT_MAX] (using two's complement mapping). int64_t AsIntN(unsigned n) const { VIXL_ASSERT(FitsInBits(n)); return ExtractSignedBitfield64(n - 1, 0, raw_bits_); } int8_t AsInt8() const { return static_cast(AsIntN(8)); } int16_t AsInt16() const { return static_cast(AsIntN(16)); } int32_t AsInt32() const { return static_cast(AsIntN(32)); } int64_t AsInt64() const { return AsIntN(64); } // Several instructions encode a signed int_t, which is then (optionally) // left-shifted and sign-extended to a Z register lane with a size which may // be larger than N. This helper tries to find an int_t such that the // IntegerOperand's arithmetic value is reproduced in each lane. // // This is the mechanism that allows `Insr(z0.VnB(), 0xff)` to be treated as // `Insr(z0.VnB(), -1)`. template bool TryEncodeAsShiftedIntNForLane(const CPURegister& zd, T* imm) const { VIXL_STATIC_ASSERT(std::numeric_limits::digits > N); VIXL_ASSERT(FitsInLane(zd)); if ((raw_bits_ & GetUintMask(kShift)) != 0) return false; // Reverse the specified left-shift. IntegerOperand unshifted(*this); unshifted.ArithmeticShiftRight(kShift); if (unshifted.IsIntN(N)) { // This is trivial, since sign-extension produces the same arithmetic // value irrespective of the destination size. *imm = static_cast(unshifted.AsIntN(N)); return true; } // Otherwise, we might be able to use the sign-extension to produce the // desired bit pattern. We can only do this for values in the range // [INT_MAX + 1, UINT_MAX], where the highest set bit is the sign bit. // // The lane size has to be adjusted to compensate for `kShift`, since the // high bits will be dropped when the encoded value is left-shifted. if (unshifted.IsUintN(zd.GetLaneSizeInBits() - kShift)) { int64_t encoded = unshifted.AsIntN(zd.GetLaneSizeInBits() - kShift); if (vixl::IsIntN(N, encoded)) { *imm = static_cast(encoded); return true; } } return false; } // As above, but `kShift` is written to the `*shift` parameter on success, so // that it is easy to chain calls like this: // // if (imm.TryEncodeAsShiftedIntNForLane<8, 0>(zd, &imm8, &shift) || // imm.TryEncodeAsShiftedIntNForLane<8, 8>(zd, &imm8, &shift)) { // insn(zd, imm8, shift) // } template bool TryEncodeAsShiftedIntNForLane(const CPURegister& zd, T* imm, S* shift) const { if (TryEncodeAsShiftedIntNForLane(zd, imm)) { *shift = kShift; return true; } return false; } // As above, but assume that `kShift` is 0. template bool TryEncodeAsIntNForLane(const CPURegister& zd, T* imm) const { return TryEncodeAsShiftedIntNForLane(zd, imm); } // As above, but for unsigned fields. This is usuaully a simple operation, but // is provided for symmetry. template bool TryEncodeAsShiftedUintNForLane(const CPURegister& zd, T* imm) const { VIXL_STATIC_ASSERT(std::numeric_limits::digits > N); VIXL_ASSERT(FitsInLane(zd)); // TODO: Should we convert -1 to 0xff here? if (is_negative_) return false; USE(zd); if ((raw_bits_ & GetUintMask(kShift)) != 0) return false; if (vixl::IsUintN(N, raw_bits_ >> kShift)) { *imm = static_cast(raw_bits_ >> kShift); return true; } return false; } template bool TryEncodeAsShiftedUintNForLane(const CPURegister& zd, T* imm, S* shift) const { if (TryEncodeAsShiftedUintNForLane(zd, imm)) { *shift = kShift; return true; } return false; } bool IsZero() const { return raw_bits_ == 0; } bool IsNegative() const { return is_negative_; } bool IsPositiveOrZero() const { return !is_negative_; } uint64_t GetMagnitude() const { return is_negative_ ? -raw_bits_ : raw_bits_; } private: // Shift the arithmetic value right, with sign extension if is_negative_. void ArithmeticShiftRight(int shift) { VIXL_ASSERT((shift >= 0) && (shift < 64)); if (shift == 0) return; if (is_negative_) { raw_bits_ = ExtractSignedBitfield64(63, shift, raw_bits_); } else { raw_bits_ >>= shift; } } uint64_t raw_bits_; bool is_negative_; }; // This an abstraction that can represent a register or memory location. The // `MacroAssembler` provides helpers to move data between generic operands. class GenericOperand { public: GenericOperand() { VIXL_ASSERT(!IsValid()); } GenericOperand(const CPURegister& reg); // NOLINT(runtime/explicit) GenericOperand(const MemOperand& mem_op, size_t mem_op_size = 0); // NOLINT(runtime/explicit) bool IsValid() const { return cpu_register_.IsValid() != mem_op_.IsValid(); } bool Equals(const GenericOperand& other) const; bool IsCPURegister() const { VIXL_ASSERT(IsValid()); return cpu_register_.IsValid(); } bool IsRegister() const { return IsCPURegister() && cpu_register_.IsRegister(); } bool IsVRegister() const { return IsCPURegister() && cpu_register_.IsVRegister(); } bool IsSameCPURegisterType(const GenericOperand& other) { return IsCPURegister() && other.IsCPURegister() && GetCPURegister().IsSameType(other.GetCPURegister()); } bool IsMemOperand() const { VIXL_ASSERT(IsValid()); return mem_op_.IsValid(); } CPURegister GetCPURegister() const { VIXL_ASSERT(IsCPURegister()); return cpu_register_; } MemOperand GetMemOperand() const { VIXL_ASSERT(IsMemOperand()); return mem_op_; } size_t GetMemOperandSizeInBytes() const { VIXL_ASSERT(IsMemOperand()); return mem_op_size_; } size_t GetSizeInBytes() const { return IsCPURegister() ? cpu_register_.GetSizeInBytes() : GetMemOperandSizeInBytes(); } size_t GetSizeInBits() const { return GetSizeInBytes() * kBitsPerByte; } private: CPURegister cpu_register_; MemOperand mem_op_; // The size of the memory region pointed to, in bytes. // We only support sizes up to X/D register sizes. size_t mem_op_size_; }; } } // namespace vixl::aarch64 #endif // VIXL_AARCH64_OPERANDS_AARCH64_H_